DYNAMIC ORDERED INHERITANCE AND FLEXIBLE

Transcription

DYNAMIC ORDERED INHERITANCE AND FLEXIBLE
DYNAMIC ORDERED INHERITANCE AND FLEXIBLE
METHOD DISPATCH
Paradigm, Concepts, and Use Cases
Dissertation zur Erlangung des akademischen Grades
Doktor der technischen Wissenschaften
in der Studienrichtung Informatik
Angefertigt am Institut für Praktische Informatik
Betreuung:
o. Univ.-Prof. Dr. Hanspeter Mössenböck
Von:
Dipl.-Ing. Gerhard Schaber
Gutachter:
o. Univ.-Prof. Dr. Hanspeter Mössenböck
o. Univ.-Prof. Dr. Wolfgang Pree
Linz, Juli 2003
© DI Gerhard Schaber, 2003
This work is subject to copyright. Some concepts and source code in this thesis are intellectual
property of WindRiver Systems, Inc., and must not be used exactly as presented here without
explicit permission of the author or WindRiver Systems. WindRiver Systems has contractually
granted all rights to use and present the concepts that are described in this thesis to the author.
All rights are reserved, whether the whole or part of the material is concerned, specifically the
commercial rights of translation, reprinting, re-use of illustrations, citation, broadcasting,
reproduction on microfilms or in any other way, and storage in data bases. Commercial
duplication of this publication in its current version, and permission for use must always be
obtained from the author. For educational, non-commercial purposes, the use and electronic
duplication of this publication is granted.
Eidesstattliche Erklärung
Ich erkläre an Eides statt, dass ich die vorliegende Dissertation selbständig und ohne fremde
Hilfe verfasst habe. Ich habe keine weiteren als die angeführten Hilfsmittel benutzt und die aus
anderen Quellen entnommenen Stellen als solche kenntlich gemacht.
Linz, 2003
Abstract
The aim of component-oriented programming is to construct large software applications out of
sets of small components with low complexity, rather than building monolithic applications
which usually are difficult and expensive to maintain and extend. This enables rapid application
development, makes maintenance easier, and therefore also reduces development costs.
Using third-party components further reduces the efforts, but it also has its shortcomings.
Developers are often confronted with the lack of flexibility of third-party components to be
adapted to their needs. Companies offering components are confronted with the problem to
anticipate how developers (customers) and users might want to adapt the components. They have
to build in hooks that allow to customize and extend the components.
The other way is to offer a solution that allows developers and users to dynamically modify
components without being limited to a few hooks. However, current approaches for component
modification are still not satisfying or sufficient. The support is often only available at compileor link-time. Current approaches that provide generic (unanticipated) support for dynamic
component modification are not sufficient.
This thesis presents a small framework which supports dynamic component modification through
a flexible method dispatch mechanism. Method dispatch is the mechanism for delegating a
method call from the object where the method has been invoked to the object, respectively class
where the method is implemented. The framework also provides proper base classes and
template configurations for components, which can help to significantly simplify development of
applications and their components. The framework is called PCoC which is the abbreviation for
Prioritized Coupling and Control of objects and components. Many of its concepts and facilities
can also be deployed independently of each other if necessary.
We use a reflection-based (meta-programming) approach where operations are treated as firstclass objects. So-called Dispatchers, similar to the Microsoft .NET delegates (type-safe methodpointers), are responsible for dynamic method dispatch. The approach enables to add, remove, or
replace operations at runtime, and to attach listeners to method objects, which are notified before
or after each call. It simulates delegation—a dispatch mechanism in which an object (the
receiver) delegates a message to another object (the method holder and delegate) in response to a
message. The delegate carries out the request on behalf of the original object, and may send
subsequent messages to the original receiver. This includes the invocation of methods.
A priority ranking of components determines in which order requests for operations are
forwarded, which gives the framework its name PCoC. This ranking can change through user
interaction (focus change in the GUI), or explicitly.
There are implementations of PCoC in C++ and Java. These are parts of two integrated
development environments (IDEs) available on various platforms, including Sun Solaris, Linux,
and the Microsoft Windows NT line.
i
Kurzfassung
Das Ziel von Komponenten-orientierter Programmierung ist es, grosse Anwendungen aus einer
Menge von kleinen Komponenten mit niedriger Komplexität zusammenzustellen, anstatt
monolithische Anwendungen zu bauen, die schwierig und teuer zu warten und erweitern sind.
Dieser Ansatz unterstützt eine schnelle Entwicklung von Anwendungsprogrammen und deren
Wartung, und senkt damit auch Entwicklungskosten.
Die Verwendung von Komponenten von Drittanbietern reduziert den Aufwand nochmals, aber
hat auch Schattenseiten. Entwickler sind oft mit einem Mangel an Flexibilität von solchen
Komponenten konfrontiert, welche sich oft nicht an Ihre speziellen Bedürfnisse anpassen lassen.
Firmen, die Komponenten anbieten, sind ihrerseits oft mit dem Problem konfrontiert,
herausfinden und voraussehen zu müssen, wie (künftige) Benutzer der Komponenten diese
erweitern und anpassen wollen.
Ein anderer Ansatz ist eine allgemeine Lösung, die es Entwicklern und Benutzern erlaubt,
Komponenten dynamisch zu verändern, ohne auf wenige vorgesehene Callback-Funktionen und
Konfigurationsmöglichkeiten beschränkt zu sein. Jedoch sind aktuelle Ansätze für dynamische
Komponentenmodifikation immer noch nicht sehr befriedigend und ausgereift. Modifikationen
sind meistens nur zur Compile- oder Linkzeit möglich. Aktuelle Ansätze für eine allgemeine
Unterstützung für dynamische Komponentenmodifikation sind noch nicht zufrieden stellend.
Diese Arbeit präsentiert ein kleines Framework, das dynamische Komponentenmodifikation
durch einen flexiblen Verteilungsmechanismus für Methodenaufrufe realisiert. Dieser
Mechanismus delegiert dynamische Methodenaufrufe von den Objekten, wo Operationen
aufgerufen werden, zu den Objekten, wo die entsprechenden Methoden implementiert sind. Das
Framework bietet auch geeignete Basisklassen und Konfigurationsvorlagen für Komponenten,
die die Entwicklung von Anwendungen und deren Komponenten um vieles vereinfachen können.
Die Bezeichnung des Frameworks ist PCoC, als Abkürzung für Prioritized Coupling and Control
of objects and components.
Wir verwenden einen Reflection-basierten Ansatz, bei dem Operationen als First-Class-Objekte
behandelt werden. Sogenannte Dispatcher, ähnlich der Microsoft .NET Delegates (typsichere
Methodenzeiger), sind für die Verteilung von dynamischen Methodenaufrufen zuständig. Der
Ansatz ermöglicht das Hinzufügen, Entfernen, und Ersetzen von Operationen zur Laufzeit, und
das Anmelden von Listener-Objekten, die vor und nach jedem Aufruf oder Statusänderung der
jeweiligen Operation benachrichtigt werden. Der Ansatz simuliert Delegation—ein
Verteilungsmechanismus für Methodenaufrufe, bei dem ein Objekt (der Empfänger eines
Aufrufes) Aufrufe zu einem oder mehreren anderen Objekten (wo die Methode implementiert
ist—der “Delegate” eines Aufrufes) weiterleitet. Ein Delegate führt die entsprechende Operation
aus und kann selbst weitere Operationen am ursprünglichen Empfängerobjekt aufrufen, welche
dann möglicherweise wiederum weitergeleitet werden.
Eine Reihung von Komponenten nach Priorität bestimmt in welcher Reihenfolge die Aufrufe von
Operationen zu welchen Komponenten weitergeleitet werden, was dem Framework seinen
Namen PCoC gibt. Diese Reihung kann einerseits durch Benutzinteraktion geändert werden
(Fokus-Änderung in der Benutzerschnittstelle, behandelt durch das Framework), oder explizit im
Client-Code.
Es gibt Implementierungen von PCoC in C++ and Java. Sie sind Teile von zwei integrierten
Entwicklungsumgebungen (IDEs) auf Sun Solaris, Linux, und der Microsoft Windows NT-Linie.
ii
Acknowledgments
First and foremost I want to express my gratitude to my advisor, Hanspeter Mössenböck. None of
this would have been possible without his guidance and support. His constructive criticism of my
thinking and writing helped to keep me on track, to develop new ideas, and to avoid pitfalls. His
guidance on literature and research I should focus on was invaluable.
I would like to thank my parents, Waltraud and Georg Schaber, who made my education possible
and supported by during my study.
I would like to express my appreciation to my sister, Karin Schaber, who gave me the inspiration
and motivation that kept me on track. I have always admired her ambition and determination, and
I still do. She has always been a person to whom I have looked up to, and a very good friend. Her
advice regarding the writing of this thesis was also most helpful.
I also would like to express my appreciation to Michael Scharf who is a great software architect.
We had many really good discussions. Our work as a team and all the nightly discussions we had
were sources for many new ideas and steady motivation. The whole framework presented in this
thesis would not have been possible without him.
My thanks go also to Werner Kurschl who supported me with good literature and advice.
Our technical writers, Jyo Samjee and Eric Strobl, helped to improve the English. I am truly
thankful for their help. I appreciate the personality of both, and our many and extensive talks.
Many thanks go to Eric who proof-read this thesis, and to Jyo who always had a good advice and
is one of my best friends.
Last, but not least, I would like to thank my former employer, WindRiver Systems GmbH, more
precisely Tomas Evensen and Rudolf Frauenschuh for giving me the permission to use concepts,
documents, and source code I created during my work for WindRiver for this thesis. I have
learned a lot in this company.
I started working for Takefive Software in Salzburg in 1997. The company was acquired first by
Integrated Systems, and then by WindRiver Systems. I want to thank all my colleagues there for
our great cooperation in the last years, and many good ideas. I appreciate all of them as great
developers and colleagues: Helmut Haigermoser (who does great work as developer, workspace
and system administrator, and Harry Potter and Lord of the Rings story teller), Walter Brunauer,
Christian Linhart (the living C++ standard—he knows everything about C++), Max Weninger,
Edi Vorauer, Robert Lichtenberger, Johann Draschwandtner, Toni Leherbauer, Martin
Gutschelhofer, Laszlo Juhasz, Rudolf Zimmel (a great project manager).
iii
iv
Contents
Abstract.............................................................................................................................................i
Kurzfassung.....................................................................................................................................ii
Acknowledgments..........................................................................................................................iii
1 Introduction...................................................................................................................................1
1.1 Motivation and Goals............................................................................................................1
1.2 A Flexible Method Dispatch Mechanism.............................................................................3
1.3 The PCoC Framework...........................................................................................................4
1.4 Deployment of PCoC............................................................................................................4
1.5 Keywords..............................................................................................................................5
1.6 Conventions..........................................................................................................................5
1.7 Defining Roles......................................................................................................................6
2 Method Dispatch and Delegation..................................................................................................7
2.1 Overview...............................................................................................................................7
2.2 State of the Art......................................................................................................................7
2.2.1 Smalltalk Method Dispatch...........................................................................................8
2.2.2 V-Tables........................................................................................................................9
2.2.3 Message Objects..........................................................................................................10
2.2.4 Delegation....................................................................................................................12
2.2.5 Type-Safe Delegation and Dynamic Component Modification..................................15
2.3 Flexible Method Dispatch...................................................................................................19
2.4 Method Dispatch Using Reflection.....................................................................................20
2.5 Dispatcher-Dictionary Implementation...............................................................................24
2.6 Dispatcher Implementation.................................................................................................25
2.7 Using Dispatcher Dictionaries and Indirect References......................................................27
2.8 Dynamically Adding Aspects to Methods...........................................................................29
2.9 Notifications........................................................................................................................31
2.10 Flexible Delegation...........................................................................................................33
2.11 Prioritized Flexible Method Dispatch...............................................................................36
2.12 Containment Hierarchy.....................................................................................................38
2.13 Remarks.............................................................................................................................39
3 PCoC Terms................................................................................................................................45
3.1 Overview.............................................................................................................................45
3.2 Activities Providers.............................................................................................................45
3.2.1 Overview.....................................................................................................................45
3.2.2 Class(es)......................................................................................................................46
3.3 Activity Sets........................................................................................................................46
3.3.1 Overview.....................................................................................................................46
3.3.2 Class(es)......................................................................................................................46
3.4 Tools....................................................................................................................................47
3.4.1 Overview.....................................................................................................................47
3.4.2 Examples.....................................................................................................................47
3.4.3 Class(es)......................................................................................................................48
3.4.4 Remarks.......................................................................................................................48
3.5 Service Providers................................................................................................................48
3.5.1 Overview.....................................................................................................................48
3.5.2 Class(es)......................................................................................................................49
3.5.3 Remarks.......................................................................................................................49
3.6 Activities.............................................................................................................................49
v
3.6.1 Overview.....................................................................................................................49
3.6.2 Class(es)......................................................................................................................50
3.6.3 Remarks.......................................................................................................................50
3.7 Materials..............................................................................................................................50
3.7.1 Overview.....................................................................................................................50
3.7.2 Class(es)......................................................................................................................51
3.8 Dispatchers..........................................................................................................................51
3.8.1 Overview.....................................................................................................................51
3.8.2 Class(es)......................................................................................................................51
3.8.3 Remarks.......................................................................................................................51
3.9 Tasks...................................................................................................................................52
3.9.1 Overview.....................................................................................................................52
3.9.2 Class(es)......................................................................................................................52
3.9.3 Remarks.......................................................................................................................52
3.10 Case Insensitivity in PCoC................................................................................................52
3.11 Class Hierarchy.................................................................................................................53
4 Using PCoC................................................................................................................................55
4.1 Overview.............................................................................................................................55
4.2 The First Tool......................................................................................................................55
4.3 Using Activities...................................................................................................................59
4.3.1 Activity Class Interface...............................................................................................59
4.3.2 Activity Listener Interface...........................................................................................60
4.3.3 PCCMaterial: Container for Arguments and Return Values.......................................61
4.3.4 Dynamic Activity Type Explained..............................................................................62
4.3.5 Creating and Setting Up Activities..............................................................................63
4.3.6 Implementing Activities..............................................................................................66
4.3.6.1 Subclassing..........................................................................................................66
4.3.6.2 Performing Activities...........................................................................................67
4.3.7 State Handling of Activities........................................................................................67
4.3.8 Fetching Context Data of Activities............................................................................69
4.4 Using Dispatchers...............................................................................................................71
4.4.1 Invoking Activities......................................................................................................71
4.4.2 State Handling of Dispatchers.....................................................................................74
4.4.3 Fetching Context Data From Dispatchers...................................................................74
4.5 Using Tasks.........................................................................................................................75
4.5.1 Overview.....................................................................................................................75
4.5.2 Simple Tasks...............................................................................................................76
4.5.3 Macros.........................................................................................................................79
4.6 Delegation using PCoC.......................................................................................................82
5 Detailed Concepts and Implementation......................................................................................87
5.1 PCoC-Related Packages and Responsibilities....................................................................87
5.1.1 Configuration...............................................................................................................88
5.1.2 Frame Manager............................................................................................................88
5.1.3 Command Manager.....................................................................................................89
5.1.4 PCoC Core...................................................................................................................89
5.1.5 Simple and Combined Tools.......................................................................................89
5.1.6 Standard Widgets / Standard Tools.............................................................................89
5.1.7 Remarks.......................................................................................................................89
5.2 Activities Provider..............................................................................................................89
5.3 Containment Hierarchy.......................................................................................................91
5.3.1 Sample Hierarchy........................................................................................................91
vi
5.3.2 Modifying a Containment Hierarchy...........................................................................92
5.4 Acquisition: Dynamic Component Inheritance...................................................................95
5.4.1 Overview.....................................................................................................................95
5.4.2 Environmental Acquisition..........................................................................................95
5.4.3 Direct Acquisition.......................................................................................................95
5.4.3.1 Acquisition in PCoC............................................................................................96
5.4.3.2 Sample Acquisition..............................................................................................97
5.4.3.3 Acquiring / Discarding a Component..................................................................99
5.4.3.4 Changing the Priorities (the Ranking) of Acquired Activity Sets.....................100
5.4.4 Remarks.....................................................................................................................101
5.5 Activities and their Interfaces...........................................................................................101
5.5.1 Interface Specification...............................................................................................101
5.5.2 Retrieving Interfaces..................................................................................................102
5.5.3 Activity Interface Class.............................................................................................102
5.5.4 Type Safety / Implementation of perform.................................................................103
5.6 Dispatchers and Activity Sets: Dispatching Requests.......................................................105
5.6.1 Activity and Dispatcher Tables.................................................................................105
5.6.2 Dispatching Activity Requests..................................................................................106
5.7 Tasks in Detail...................................................................................................................109
5.7.1 Structure of Tasks......................................................................................................109
5.7.2 Dispatching Requests using Tasks............................................................................111
5.8 Activity Sets: Putting Everything Together......................................................................112
5.8.1 Architecture Overview..............................................................................................112
5.8.2 Dispatching Activity Requests in Activity Sets........................................................114
5.8.3 Dispatcher Proxy Management.................................................................................114
5.9 Implementation Issues.......................................................................................................116
5.9.1 Java or C++?..............................................................................................................116
5.9.2 Supporting Implicit Generation of Activities............................................................116
5.9.3 Explicit and Semi-automatic Generation of Activities..............................................119
6 Comparison with Related Approaches......................................................................................123
6.1 Environmental Acquisition...............................................................................................123
6.1.1 Overview of Environmental Acquisition...................................................................123
6.1.2 Environmental Acquisition in Literature...................................................................123
6.1.3 Simple Implementation.............................................................................................124
6.1.4 Implicit Acquisition...................................................................................................126
6.1.5 Explicit Acquisition...................................................................................................127
6.1.6 Remarks.....................................................................................................................127
6.2 Aspect Oriented Programming..........................................................................................127
6.2.1 Overview...................................................................................................................127
6.2.2 AspectJ......................................................................................................................129
6.2.3 Other Mechanisms.....................................................................................................131
6.2.4 Remarks.....................................................................................................................133
6.3 Event Channeling..............................................................................................................133
6.4 Java Reflection..................................................................................................................134
6.4.1 Overview...................................................................................................................134
6.4.2 Reflecting Classes and Methods in Java...................................................................134
6.4.3 Java Type Checking...................................................................................................135
6.4.4 Dynamic Method Invocation in Java.........................................................................135
6.4.5 Java Reflection: Invoking Constructors....................................................................136
6.4.6 Java Reflection: Fields..............................................................................................136
6.4.7 Dynamic Classes in Java: Class Proxy......................................................................137
vii
6.4.7.1 Overview............................................................................................................137
6.4.7.2 Methods Duplicated in Multiple Proxy Interfaces.............................................141
6.4.7.3 Remarks.............................................................................................................141
6.5 Java Swing Actions...........................................................................................................142
6.5.1 Overview...................................................................................................................142
6.5.2 Using Java Swing Actions.........................................................................................143
6.5.3 Using Actions for Forwarding...................................................................................145
6.5.4 Remarks.....................................................................................................................147
6.6 Method Pointers in C++....................................................................................................148
6.7 Microsoft .NET.................................................................................................................149
6.7.1 Overview...................................................................................................................149
6.7.2 Critical issues of .NET..............................................................................................150
6.7.3 Language Independent Object Model of .NET..........................................................150
6.7.4 Overview of C#.........................................................................................................150
6.7.5 .NET Delegates..........................................................................................................151
6.7.5.1 Overview............................................................................................................151
6.7.5.2 Using Delegates.................................................................................................152
6.7.5.3 Remarks.............................................................................................................155
6.7.6 .NET Late Binding and Dynamic Method Invocation...............................................156
6.7.6.1 Overview............................................................................................................156
6.7.6.2 Dynamic Method Invocation.............................................................................156
6.7.6.3 Remarks.............................................................................................................157
6.7.7 Name Spaces on .NET...............................................................................................158
6.7.7.1 Overview............................................................................................................158
6.7.7.2 Using Name Spaces...........................................................................................158
6.7.7.3 Name-Space Aliases..........................................................................................159
6.7.7.4 Nested Name Spaces..........................................................................................160
6.7.7.5 Remarks.............................................................................................................160
6.7.8 .NET Properties.........................................................................................................161
6.7.9 Remarks.....................................................................................................................162
6.8 Smalltalk...........................................................................................................................163
6.9 Oberon Message Objects...................................................................................................164
6.9.1 Overview...................................................................................................................164
6.9.2 Remarks.....................................................................................................................164
6.10 Self..................................................................................................................................166
6.10.1 Overview.................................................................................................................166
6.10.2 Prototyping vs. Class Inheritance............................................................................166
6.10.3 Prioritized Multiple Inheritance..............................................................................168
6.10.3.1 Overview..........................................................................................................168
6.10.3.2 Design Principles.............................................................................................170
6.10.4 Remarks...................................................................................................................172
6.11 System Object Model (SOM, by IBM)...........................................................................173
6.12 Design Patterns................................................................................................................174
6.12.1 Command Pattern....................................................................................................174
6.12.2 Strategy Pattern........................................................................................................174
6.12.3 Activities: First Class Objects.................................................................................174
6.12.4 Dispatchers..............................................................................................................175
7 Conclusion................................................................................................................................177
7.1 Overview...........................................................................................................................177
7.2 Which Mechanisms When?..............................................................................................177
7.2.1 Static Interfaces / Methods .......................................................................................177
viii
7.2.2 Events........................................................................................................................178
7.2.3 PCoC / Dynamic Dispatch ........................................................................................178
7.3 Remarks.............................................................................................................................179
7.4 Discussion.........................................................................................................................180
7.5 Future Work......................................................................................................................181
ix
x
1 Introduction
1
1 Introduction
1.1 Motivation and Goals
In order to simplify the development of large applications, they can be constructed out of small
components with low complexity. The assembly of applications from existing components
should increase reuse, thus allowing developers to concentrate on value-added tasks and to
produce software with high quality within a shorter time.
In the literature, the term software component is diversely defined. The notion of the term as
found in [SZYPE98] on Page 34, is in line with this thesis. The book provides a detailed
discussion about what a component is and what is not. Other definitions of the term and
comments on the definitions can be found on Pages 164-168.
“A software component is a unit of composition with contractually specified
interfaces and explicit context dependencies only. A software component can be
deployed independently and is subject to composition by third parties”.
With this definition we find a comment that a software component must be a binary unit. We
agree with this notion, but we use the term also for sets of class(es) together with a proper
configuration (how a component or application is customized) from which a software component
is built.
Component oriented programming promises the flexibility to adapt ready-made components to
the user’s needs. Component modification includes the replacement of components and
operations by others that are better suited for specific tasks. It also includes the customization of
the look & feel and other properties of components and whole applications.
When developing components, we often encounter one or more of the following requirements:
v the ability to add, remove, and replace data sources and targets at runtime
v the ability to add, remove, and modify functionality at runtime
v multiple reuse of components as instances in different applications or within the same
application
v priority management, for example for interactive components and their operations
v single, broadcast, and filtered dispatch of operation requests
v deferred execution of operations
v application control via menus, toolbars, scripting, and remote requests—preferably without
the need to implement separate dispatch functions for each operation and their different
deployments
The need for the ability to dynamically add and remove data sources and targets is given in
almost every interactive application. Think of a file-browser as source component, in which we
interactively select a particular file. We might need to dynamically associate a specific target
component with the selected file, depending on its file type. The target component may be a part
of a newly installed application, and should be loaded when we double-click the file in the filebrowser.
It is sometimes necessary to extend the associated target component by additional functionality.
For example, we might want to extend a word-processor component by functionality to calculate
particular statistics or to do additional highlighting of special phrases of a text document, which
is not supported by the original component. In the case of third-party components, we usually do
not have the source code which we could modify, so we need a mechanism that enables us to
2
1 Introduction
modify components dynamically. The ability to extend and modify a component is called
dynamic component modification, or dynamic component adaptation.
In order to get a single look and feel for applications, and to save development time and costs, we
should be able to reuse components in different applications or within the same application.
In certain cases it is required to switch between several implementations of an operation, class or
whole component, and to introduce a ranking for the different implementations. For example,
when applying visual effects on an image, or filters in a text-search dialog, then it is relevant in
which order the effects or filters are applied. If the effects and filters are realized as classes with a
method apply, we need assemblies, or more precisely ordered collections, where their instances
can be put into. The ranking within such an assembly could determine in which order the effects,
respectively filters, are applied. The user could interactively change the ranking, as well as the
developer could change it in source code.
Special method dispatch strategies are needed for certain use cases. To invoke single methods is
the most common case, but also broadcasts (multicasts) are needed, for example, for shutting
down all services and saving all documents when an application is going to be terminated. In
some cases, multicast messages should be sent only to certain components. We call this dispatch
mode filtered broadcast.
It is necessary that time-consuming tasks are executed asynchronously, usually in a separate
thread. This concept, called deferred execution or late invocation, helps to prevent that the
corresponding application, respectively its current thread, is blocked, as it would be the case if
the task was executed synchronously.
Most applications have a user interface, a scripting interface, and maybe an interface to control
them remotely, for example via Web-browsers. Usually, for each interface and required
operation, proper dispatch functions or hooks (callback methods) must be implemented. In
addition to that, the same operations may be invoked from various places in the source code. To
simplify development, it is reasonable to introduce a uniform method dispatch mechanism for
handling all of these interfaces, and which is easy to adapt to new interfaces. The component
developer can then concentrate on implementing the core functionality (the purpose) rather than
having also to handle all kinds of interfaces over which the component functionality is exposed.
In addition to the already mentioned requirements, some applications have to be portable to
various platforms. An architecture is required that is able to cope with small as well as huge
applications, providing easy concepts and usage. In order to be able to find defects in the
software, a facility to inspect objects and classes at runtime can be helpful. Such a facility to
display information about objects is called introspection and is based on reflection. Reflection is
the ability to obtain information about the fields, constructors and methods of any class or
component. See Developing JAVA Beans [ENGLA97] on Pages 40-42 for a short introduction
into Java reflection.
In short, we need a flexible, scalable, and easy to use approach for dynamic component
modification with support for introspection.
In [KNIES99] on Pages 1f, Kniesel G. classifies known approaches of dynamic component
modification according to:
v their need for preexisting hooks in the application as either suitable for anticipated or
unanticipated changes
v the time when a modification is made as either static, load-time or dynamic (runtime)
v their ability to adapt whole component types or individual component instances as either
global or selective. Selective approaches can be further classified as either replacing or
preserving, depending on whether they replace an existing component instance by its
modified version or let both be used simultaneously.
1.1 Motivation and Goals
3
v the applied techniques as either code-modification-based, wrapper-based or meta-levelbased.
The need of unanticipated dynamic component modification has been repeatedly pointed out in
literature (e.g. [MAETZ97]).
When developing a component, we may not always know which hooks might be needed in the
future by users of the component. We cannot always anticipate which enhancements users might
want to make, and we usually do not want to make the source code available.
Global approaches affect classes, whereas selective approaches affect objects. A modification of
a class has an impact on all its instances, whereas a modification of an object has only a local
impact.
By introducing wrappers for components, the interfaces and functionality can be adapted to the
client’s needs without changing the original components. A wrapper acts as proxy (a surrogate or
placeholder) of its associated component, and may load it on demand. If the component cannot
be loaded, the wrapper can throw an error.
A more detailed discussion of this classification is given in Section 2.2.5.
Our goal is an approach that offers support for dynamic component modification and which
meets the requirements mentioned earlier in this section.
1.2 A Flexible Method Dispatch Mechanism
With respect to the classification above, we introduce a flexible method dispatch mechanism that
offers support for dynamic, selective, wrapper-based component modification. This approach is
object-preserving when applied at instance-level (a generic component-instance wrapper handles
dynamic operations; such an operation is represented as object with a reference to the wrapper).
When integrated at class-level (wrappers for classes rather than instances; operations do not have
a reference to a wrapper), it is global and meta-level-based. The flexible method dispatch is
based on reflection, more precisely on dynamic method calls. No proprietary compiler and
preprocessing of source code is necessary.
The approach enables to add, remove, or replace operations (method objects) at runtime, and to
attach listeners to operations, which are notified before or after each call. We call these
operations Activities. An Activity can either be directly added to a so-called Activity Set, or
acquired from another. In our approach, each component is associated with an Activity Set.
Acquisition denotes a delegation mechanism. That is, when an Activity Set (the child) acquires
from another (the parent), and an operation is called on the child, and the child does not provide
the operation, the call is delegated to the parent. A reference to the receiver (where the operation
has been invoked) is passed with each operation, so that the parent can invoke subsequent
operations on the original receiver (the child).
The difference to what we understand as delegation is that our approach uses sets of dynamic
operations (Activity Sets) rather than objects. However, the dispatch algorithm is the same.
As opposed to class inheritance which is used to share behavior between classes, delegation (and
acquisition) is used to share behavior between objects. The delegation concept is described in
detail in Section 2.2.4.
Activities are treated as first-class objects. So-called Dispatchers, similar to the Microsoft .NET
delegates (type-safe method-pointers), are responsible for dynamic method dispatch, or more
precisely for delegating operation requests. The main difference to delegates is that Dispatchers
can be used for delegation, whereas delegates can only be used for forwarding. With forwarding,
the object where a method originally has been invoked (the receiver) is not known to the method
holder (where the method is actually executed), and therefore the method holder cannot use it for
subsequent method calls.
4
1 Introduction
Dispatchers are automatically generated when an Activity is added to a component, respectively
to its associated Activity Set, or acquired from another. Their type safety is ensured at runtime,
whereas the type safety of .NET delegates is ensured at compile time.
Activities can be extended by aspects at runtime. Aspects are well-modularized reusable crosscutting concerns in software: Code fragments can sometimes be separated into their main
functionality and several aspects. Such an aspect applies to a set of code fragments, and thus is a
means of reusability. Examples are plausibility checks for method arguments and return values.
They also include tracing capabilities, additional data-types for algorithms, etc.
“Aspect oriented programming does for concerns that are naturally cross-cutting what
object-oriented programming does for concerns that are naturally hierarchical.” [KICZAL00], Page 1.
1.3 The PCoC Framework
We present a small framework that supports the flexible method dispatch mechanism, and meets
the requirements discussed earlier in this chapter. The framework also provides proper base
classes and template configurations for components, which can help to significantly simplify
development of applications and their components. The method dispatch mechanism including
its support for different component interfaces is encapsulated in the base classes, as well as the
processing of component startup, initialization, and termination. This approach allows
developers to concentrate on value-added tasks such as implementing the core functionality (the
purpose) of each component.
The framework is called PCoC as abbreviation for Prioritized Coupling and Control of objects
and components. The name denotes the support for prioritized dynamic inheritance (acquisition),
and the already mentioned generic, flexible method dispatch mechanism used for component
interoperation (coupling) and application control. A priority ranking of acquired components
determines in which order requests for operations are delegated. This ranking can change through
user interaction (focus change in the GUI), or explicitly.
All operations of all components are automatically exposed in a generic way for their use in the
GUI (menu bars, tool bars, etc.), for scripting, for component interoperation, and for remote
control via XMLRPC (XML-based remote procedure calls), etc.
Many of the concepts and facilities of PCoC can be deployed independently of each other if
necessary. There are implementations of the framework in C++ and Java. These are parts of two
integrated development environments (IDEs) that have been released on various platforms,
including Sun Solaris, Linux, and the Microsoft Windows NT line.
The Java version is used as a basis for this thesis.
Some concepts and source code in this thesis are intellectual property of WindRiver Systems,
Inc., and must not be used exactly as presented here without explicit permission of the author or
WindRiver Systems. However, they may help to find similar solutions. WindRiver Systems has
contractually granted all rights to use and present the concepts that are described in this thesis to
the author.
1.4 Deployment of PCoC
PCoC is made for all kinds of applications that require extensive user interaction or scripting.
The goal is to provide a comfortable framework that reduces the implementation overhead for
component interoperation and controlling by separating the component assembly from source
code, providing built-in priority management of components, and a mechanism for dynamically
dispatching method calls based on this priority management.
1.4 Deployment of PCoC
5
Priority management means that components are ranked by time of last usage. The most recently
used component gets the highest priority. When sending a message to a group of components, the
message is sent to the first component in the ranking that is interested in this message and which
subsequently performs an operation associated with the message.
PCoC is not a competing component standard to CORBA, COM, .NET, JavaBeans, etc. Rather,
it works together with, or on top of, these standards.
It is also not a reasonable basis for applications without a graphical user interface, for
applications providing only one interactive component, or generally for limited-functionality
applications. Although the framework would be a too big an overhead for such small
applications, it may nevertheless be used to quickly achieve a running and extendable
application.
When developing smaller applications, in most cases standard operations (methods, functions)
and component interfaces would be sufficient. When developing larger applications (over 20
components), developers often miss mechanisms for organizing all the components of an
application and their operations.
PCoC addresses this issue by providing mechanisms for dynamic method dispatch over different
components, organizing components in a logical containment relationship, prioritizing them and
their operations, automatically exposing operations via an application plugin interface, and
defining execution relevant states per operation, e.g. “Enabled”, “Disabled”.
Composition and coupling (connecting for interoperation) of components is configurable,
maximizing reuse. To keep the customization effort low, the visual representation of different
operations of the same kind, for example, operation copy provided by many components, can be
defined in a uniform way. A definition in the configuration can be shared by any operations. It
can include text strings for menu and toolbar entries, the help string in a status line, an icon, and
also associated keyboard shortcuts. The whole configuration of an application can be modified
and reloaded at runtime.
Concepts presented in this thesis need not be used as a monolithic package. Some of the concepts
and patterns are versatile, meeting requirements also in fields other than IDE development.
1.5 Keywords
Dynamic Coupling, Component Interoperation, Dynamic Component Modification, Dynamic
Component Adaption, Menu and Toolbar Setup, Scripting, Application Control, (Priority, Focus)
Management, Activity, Delegate, (Method, Request, Message) Dispatching, Dynamic
Inheritance, Acquisition, Delegation, Very Late Binding, Meta-Programming, Reflection, Code
Reuse, Object-Oriented, Component-Oriented, Application Framework, IDE, Tools, Services.
1.6 Conventions
The following typefaces are used in this thesis:
For source code, mono-space (Courier New)
v Example: getX
To emphasize a word or text, or to introduce terminology, italics
v Example: An application may consist of components called Tools and Service Providers.
Types begin with a capital letter, operation names with a lower case letter.
v Examples: Point, getX
6
1 Introduction
Classes of the PCoC framework have the prefix “PCC”. This prefix is sometimes omitted for
simplicity in diagrams, figures, and text sections where the context is self-explanatory.
v Example: PCCDispatcher
1.7 Defining Roles
We distinguish the following roles for developers in this thesis:
1. Framework developer: Responsible for the concepts and the architecture behind an
application.
2. Component developer: Develops components without needing to know much about the
architecture and how messages are sent between components to perform operations. There
is also not much need to know about customizing the representation of operations in the
application’s menu or toolbars. The component developer concentrates on developing the
functional part of a component.
3. Customizer / Configurator. Assembles components to an application in a reasonable
manner and configures, for example, menus and toolbars.
2 Method Dispatch and Delegation
7
2 Method Dispatch and Delegation
2.1 Overview
This chapter gives a short overview of the Smalltalk, C++/Java, and Oberon method dispatch
mechanisms, and some thoughts that lead to the development of new concepts such as a flexible
method dispatch mechanism.
We propose a solution to make inheritance relationships and scopes more flexible. The approach
is useful for dynamically adding operations to objects or for dynamically changing inheritance
relationships such as replacing parent classes, respective parent objects.
The concepts introduced in this section are the basis for the PCoC framework described later in
this thesis.
2.2 State of the Art
Two well-known method dispatch facilities are the Smalltalk method dictionaries and the
C++/Java virtual tables. Method dispatch is a mechanism to forward method calls to the classes
where the methods actually are implemented. It comprises two basic steps: the method look-up in
a method dictionary or v-table, and the execution in the corresponding class. The process of
finding the class where a method is implemented, and the subsequent invocation on this class is
similar to delegation. See later in this section.
The Smalltalk method dictionaries are dictionaries (key/value maps) containing references to
methods of a class. Each class has one such method dictionary. When invoking a method,
respective when sending a message to invoke it, the corresponding method is searched by name
in the class hierarchy of the corresponding object. This method dispatch is done by a component
called message handler, respectively dispatcher. This mechanism is very flexible, since all
method invocation is done using messages, and methods are dispatched by name; methods can be
altered, added or removed at runtime. This flexibility has its price: this mechanism is slower than
v-tables (see later in this section).
As optimization, Smalltalk holds a cache of recently sent messages; according to [KRASN84],
an appropriate method cache can have hit ratios as high as 95%, reduce method lookup time by a
factor of 9, and increase overall method system speed by 37%.
The Smalltalk method dispatch mechanism is described in detail in Section 2.2.1.
More efficient than the Smalltalk method dispatch using method dictionaries are v-tables (virtual
method tables)—arrays where the method pointers of a class and its base classes are stored, and
the indexes of the method pointers are calculated at compile time. Method lookup in C++ works
without any kind of engine or dispatcher. A method call is just a call to the method pointer at the
corresponding index in the v-table. The compiler generates code to execute the method. No
further look-up has to be done. This mechanism is fast, but not very flexible.
“Adding or removing a C++ method requires recompilation of potentially many vtables to preserve their parallel structure. And although we know that polymorphic
client code does not need to be rewritten, it must still be recompiled to account for
new offsets to the pointers in the tables. This recompilation overhead discourages
exploratory programming. On the other hand, C++ cooperates readily with foreign
languages, while Smalltalk’s runtime engine gets in the way of calling into or out of
the Smalltalk image.” - [LIU96], Pages 183ff (Section 16.2).
A detailed description of v-tables is given in Section 2.2.2.
8
2 Method Dispatch and Delegation
The method dispatch facilities have one thing in common—delegation. Delegation is a dispatch
mechanism originally introduced by Henry Lieberman in [LIEBER86]. As opposed to
forwarding, with delegation a method can always refer to the object where the method has
originally been invoked, regardless of the number of indirections due to object composition or
class inheritance. Delegation is relevant for method calls in object-oriented languages, but also
for component interoperation. This mechanism is explained by means of virtual method calls in
Section 2.2.4.
Methods are not the only way to handle messages. The Oberon system (Oberon is a descendant
of the programming language Modula-2, which itself followed Pascal) supports, besides ordinary
methods, so-called message objects. Message objects are data packages. They are subject to be
passed to so-called method interpreters (special methods) for processing. A method interpreter
may implement functionality for certain message types; it may also ignore message objects of
some types, or forward message objects to other message interpreters.
Object-oriented programming with message objects is similar to the way how Smalltalk
dispatches method calls; the main difference is that in Smalltalk the dispatcher is integrated in
the system, and in Oberon the message interpreter must be implemented per object by the
developer. Smalltalk holds method references in method dictionaries; this can be implemented
for each message interpreter in Oberon. Results are stored in the message object passed to a
method. An overview of Oberon message objects is given in Section 2.2.3. H. Mössenböck
describes the Oberon message objects in more detail in [MÖSS95], on Pages 127ff (German
edition: [MÖSS98]).
2.2.1 Smalltalk Method Dispatch
Each Smalltalk class has a dictionary (key/value-map) that contains references to the methods of
the class. When a method is invoked, it is first searched in the dictionary of the object’s class. If
it is not found there, it is searched in the base classes until it is found.
To illustrate how the Smalltalk method dispatch works in detail, let us take a look at the
following example.
object p
p : MyPoint
class MyPoint
<<instance of>>
class Object
<<derived from>>
MyPoint
<<derived from>>
Object
nil
<<ref to class>>
<<ref to superclass>>
<<ref to superclass>>
x : int
...
...
y : int
<<dispatch>>
method
dictionary
<<dispatch>>
int equals(Object)
int hashCode()
int getX()
int equals(Object)
setX(int)
...
int getY()
<<dispatch>>
setY(int)
<<dispatch>>
method
dictionary
getX
method code
int hashCode()
method code
int getX()
int equals()
method
code
method code
executable methods
executable methods
Figure 2.2-1 Method dispatch as in Smalltalk
In the given example we see object p, its class MyPoint, and the base class (superclass) of
MyPoint, Object. MyPoint defines the methods equals, getX, setX, getY, and setY,
i.e. its method dictionary contains references to these methods. Object defines, for example,
the methods hashCode and equals.
2.2 State of the Art
9
In Smalltalk, method invocation comprises the following steps:
1. Get the class object
(MyPoint) of the object (p) where the method (getX) was invoked
2. Get the method dictionary
The entries in this key/value map are pointers, respectively references, to methods.
3. Look up the method in the method dictionary of the current class
4. If found, invoke the method code (getX) and go to step 7
5. If not found then continue with the base class (Object) at step 2
6. If there is no base class, then throw an error and go to step 7
7. Done
In our example, the class object is MyPoint. When calling setX, the method is searched and
found directly in the class of p, MyPoint (light grey path in Figure 2.2-1). In the case of method
hashCode, no method reference can be found in class MyPoint, so the search is continued in
the base class Object. Finally, hashCode is found and executed there (dark grey path in
Figure 2.2-1).
2.2.2 V-Tables
V-tables (virtual method tables) are arrays holding method pointers of a class and its base
classes. Each method corresponds to an index in a v-table. The indexes are calculated at compile
time. When a method is invoked on an object, the method pointer at the corresponding index in
the v-table of the object’s class is retrieved and the method finally executed.
Let us take a look at the following example to illustrate how v-table method dispatch works.
object p
p : MyPoint
class MyPoint
<<instance of>>
class Object
<<derived from>>
MyPoint
Object
<<pointer to class>>
<<pointer to base class>>
<<pointer to base class>>
x : int
...
...
<<derived from>>
nil
y : int
<<dispatch>>
0
int hashCode()
1 int equals(Object)
2..n-1
v-table
...
<<dispatch>>
0
int hashCode()
1
int equals(Object)
v-table
...
2..n-1
n
int getX()
n+1
setX(int)
n+2
int getY()
getX
method code
int hashCode()
method code
n+3
setY(int)
executable methods
<<dispatch>>
<<dispatch>>
int getX()
int equals()
method
code
method code
executable methods
Figure 2.2-2 V-tables
Figure 2.2-2 shows an object p and its class MyPoint and the base class Object including
their v-tables. Note that the indexes of method pointers for the v-tables are calculated by the
compiler.
10
2 Method Dispatch and Delegation
A v-table always starts with the v-table-entries of the base class(es), but contains also the
pointers to the methods of the actual class. The indexes of the method pointers of base classes are
always the same as in the base classes. For example, in Figure 2.2-2 the pointer to method
hashCode may get the index 0 (calculated by the compiler) in the v-table of Object, so
hashCode has also index 0 in the v-tables of all classes derived from Object. So, in Figure
2.2-2 the v-table of MyPoint starts with the method pointer to hashCode. After the Object
method pointers we find the pointers to the methods of MyPoint, e.g. getX, etc.
An equivalent implementation of MyPoint in Java can be found in Program 2.2-5.
The method invocation using v-tables comprises the following steps:
1. Get the class object
(MyPoint) of the object (p) where the method (getX) has been invoked
2. Get the v-table
The value entries in this array are pointers to methods.
3. Get the method pointer at the index represented by method getX. The index was already
calculated at compile-time, so you are actually not calling method getX, but method 3
(assuming a class structure as in Figure 2.2-2)
4. Invoke the corresponding method, with object p as implicit parameter (“this”).
5. Done
Basically, method look-up reduces to retrieving the method pointer at the index for the invoked
method in the v-table of the current class. This mechanism is fast, but not very flexible; since vtables are created at compile-time, there is no way to add or remove methods at runtime. Even if
v-table information could be changed at runtime, it would carry some difficulties; if we added or
removed a method in a base class, it would be necessary to reorganize the v-table indexes of all
derived classes. This would be very time-consuming. With multiple base classes, this is even
more difficult—the index of a method must be the same in each base class, derived class, and the
current class providing the method.
Besides in object-oriented languages as Java or C++, the v-table concept is also used, for
example, in the Microsoft component standard COM and its derivatives (DCOM, etc.). Clemens
Szyperski has a review of these component standards in [SZYPE98] on Pages 194ff.
2.2.3 Message Objects
Oberon provides, besides ordinary method dispatch via v-tables, the concept of message objects
and interpreters. Message objects are data packages that can be sent to message interpreters
(message handlers) in order to be processed. As method of an object, a message interpreter may
implement functionality for some message types; it may also ignore messages or forward them to
other interpreters.
This concept needs one or many message interpreters responsible for handling incoming message
objects. The message handler analyzes the dynamic type of a message and performs a specific
operation accordingly.
Let us take a look at some sample code. First, we define some message types:
Program 2.2-1 Message Types
TYPE
Message = RECORD END; (* empty message, base type for all messages *)
SetXMessage = RECORD (Message) x: INTEGER END; (* a concrete message *)
GetXMessage = RECORD (Message) x: INTEGER END; (* a concrete message *)
2.2 State of the Art
11
As base class of our messages, we define the type Message. Concrete types are
SetXMessage and GetXMessage. Both declare a member variable x.
To send a message to an object, we write:
Program 2.2-2 Sending messages
...
VAR
setXMsg: SetXMessage;
getXMsg: GetXMessage;
result: INTEGER;
setMsg.x := 10;
obj.Handle(setMsg);
obj.Handle(getMsg);
result := obj.x;
...
We create instances of types SetXMessage and GetXMessage. Furthermore, we initialize x,
and pass the message object setXMsg instance to the message interpreter Handle of object
obj (it can be any object of any type). Then we pass the message object getXMsg to Handle.
Finally, we assign the result of the processed getXMsg to result.
Program 2.2-3 Interpreting (handling) messages
PROCEDURE (VAR m: Message) Handle;
BEGIN
WITH
m: SetXMessage DO
... (* do something *)
|m: GetXMessage DO
... (* do something else *)
|m: ... (* handle another message *)
ELSE
(* ignore other messages *)
END
END Handle;
Program 2.2-3 is a simple dispatch code in Oberon. Each message type has a meaning; it usually
stands for an operation. However, the actual behavior (the code that is to be executed) is
determined by the dispatch code. The content of a message object is data needed to process the
message; in our case the SetXMessage carries a value to set the x-position of a point-object,
for example, to move a graphical object to that position.
In some cases it may make sense to forward messages to other objects, for example to a child
object of a graphical composite-object. Assuming that object obj of Program 2.2-2 is such a
composite object containing two rectangles, we can add the following statement to the
SetXMessage section of the WITH-DO-clause of Program 2.2-3:
Program 2.2-4 Sending messages
...
PROCEDURE (VAR m: Message) Handle;
BEGIN
WITH
m: SetXMessage DO
childRect.Handle(m);
childRect2.Handle(m);
|m: GetXMessage DO
... (* do something else *)
...
12
2 Method Dispatch and Delegation
where childRect and childRect2 also must implement the method Handle in order to
handle message SetXMessage.
This is a powerful way to forward messages, including the possibility to forward them through
objects which do not implement all methods associated with the given messages.
Message objects have some advantages over methods ([MÖSS98]):
v Messages are data packages; they can be stored and forwarded later on.
v A message object can be passed to a method that forwards it to different objects. This
allows broadcasts, which are not or difficult to realize with methods.
v It is sometimes easier if the sender does not have to care about whether a receiver
understands a message (implements a corresponding method) or not.
v It is also possible to access the message interpreter (handler) through a procedure variable,
which enables us to replace it by another at runtime.
Message objects also have disadvantages:
v The interface of a class does not reflect which messages can be handled by instances of the
class. It is difficult to realize at compile time which objects are related through message
forwarding at runtime.
v Messages are interpreted and forwarded at runtime, which is much slower than direct
method calls. It depends on how fast dynamic type information is evaluated, or in the case
of handling messages using their names, how fast strings are parsed.
v Sending message objects requires more code to be written. Arguments must be packed into
the message object. A regular interface such as for methods is not available.
v Invalid messages are not recognized at compile time. Although this provides the flexibility
to forward messages through objects that cannot handle them themselves, it can be
troublesome to find errors.
Generally one should use methods rather than message objects. However, in some cases it is
useful to use message objects (see advantages).
2.2.4 Delegation
Delegation is a dispatch mechanism to share behavior between objects. An object (the receiver)
delegates a message to another object (the method holder and delegate) in response to a message.
The delegate carries out the request on behalf of the original object, and may send subsequent
messages to the original receiver. This includes the invocation of methods. Using delegation, a
method can always refer to the object on which it has originally been invoked, regardless of the
number of indirections due to object composition or class inheritance.
Basically, there are two types of methods: virtual and non-virtual methods. Virtual methods are
dynamically bound methods. In a virtual method invocation, the runtime type of the instance for
which the invocation takes place determines the actual method implementation to invoke. In a
non-virtual method (statically bound method) invocation, the compile-time type of the instance is
the determining factor. The implementation of a non-virtual method is invariant: The
implementation is the same whether the method is invoked on an instance of the class in which it
is declared or an instance of a derived class. In contrast, the implementation of a virtual method
can be superseded by derived classes. The process of superseding the implementation of an
inherited virtual method is known as overriding the method.
Messages are commonly used to send information from one object or component to another, for
example to execute a method on an object. Basically, there are two ways to send messages:
forwarding and delegation. The following picture illustrates the difference between these two.
2.2 State of the Art
13
this
message sender
(the caller of a method)
message receiver
delegation
(the receiver of a method call)
method holder
(where the method
is implemented and
finally executed)
this
message sender
(the caller of a method)
message receiver
forwarding
(the receiver of a method call)
method holder
(where the method
is implemented and
finally executed)
Figure 2.2-3Delegation versus Forwarding
The receiver of a message, or more precisely the object where a method is invoked, may delegate
the method call to a base class, if its own class cannot handle it. The class that implements the
method, i.e. the class where the method is finally executed, is called method holder. In contrast to
simple forwarding, with delegation, messages to “this” or “self” are sent to the original message
receiver. Note that many sources confuse delegation with forwarding. For instance, .NET
delegates simply forward method calls, although the name may imply another meaning. Some
sources describe delegation as message dispatch concept for prototypes (cloning instead of
deriving from classes) only. We think, this concept is similar to the method lookup algorithms in
class relationships (Smalltalk method dispatch, v-tables).
Let us take a look at the invocation of ordinary virtual methods.
invoking methods of MyPoint3D instances
classes:
this
MyPoint3D
int equals(Object)
MyPoint
int equals(Object)
Object
int hashCode()
int getZ()
int getX()
setX(int)
int getY()
setZ(int)
set(int, int, int)
setY(int)
int equals(Object)
Figure 2.2-4 Virtual method calls
In our example (Figure 2.2-4), we find three classes: MyPoint3D (top row), its base class
MyPoint (middle), and the base class Object (bottom). Each instance of class MyPoint3D
combines all three classes to a unit. In the scope of these classes, the implicit parameter “this”
(C++, Java) or “self” (Smalltalk) refers to the current instance (the object where a method has
been invoked).
14
2 Method Dispatch and Delegation
Program 2.2-5 Class MyPoint
public class MyPoint extends Object {
private int x;
private int y;
public MyPoint() { x = 0; y = 0; }
public int equals(Object obj) {
if (obj != null && obj instanceof MyPoint) {
MyPoint p2 = (MyPoint)obj;
if (getX() != p2.getX() || getY() != p2.getY()) { return -1; }
}
return super.equals(obj);
}
public int getX() { return x; }
public void setX(int x) { this.x = x; }
public int getY() { return y; }
public void setY(int y) { this.y = y; }
}
We use Program 2.2-5 as implementation of MyPoint; MyPoint3D adds the methods getZ
and setZ, and overrides the virtual method equals.
Program 2.2-6 Using MyPoint
MyPoint p = new MyPoint();
int val = p.getX();
int hc = p.hashCode();
In the example above, we call various methods of a MyPoint instance.
Program 2.2-7 Class MyPoint3D
public class MyPoint3D extends MyPoint {
private int z;
public MyPoint() { z = 0; }
public int equals(Object obj) {
if (obj != null && obj instanceof MyPoint3D) {
MyPoint3D p2 = (MyPoint3D)obj;
if (getZ() != p2.getZ()) { return -1; }
}
return super.equals(obj);
}
public int getZ() { return z; }
public void setZ(int z) { this.z = z; }
}
In this example, the method equals of MyPoint3D calls the equals method of MyPoint,
which in turn calls equals of Object; all calls are executed at the same object (“this”).
The class may be used as follows.
Program 2.2-8 A method call
MyPoint3D p = new MyPoint3D();
MyPoint3D p2 = new MyPoint3D();
int result = p.equals(p2);
int resultX = p.getX();
Since p has the static type (the type at compile-time) MyPoint3D. The method equals is
executed in MyPoint3D, no matter, if it is a virtual or non-virtual method, since MyPoint3D
itself provides it. If equals did not exist in MyPoint3D, calls to it would be forwarded to the
base class (MyPoint), and so forth until a base class is found that provides the corresponding
implementation. For example, a call to getX must be forwarded this way. getX is searched in
2.2 State of the Art
15
the scope of MyPoint3D; since it is not implemented there, the call is forwarded to MyPoint,
where it is finally executed.
Program 2.2-9 A method call (2)
MyPoint p = new MyPoint3D();
MyPoint p2 = new MyPoint3D();
int result = p.equals(p2);
int resultX = p.getX();
In Program 2.2-9, we assign MyPoint3D instances to variables of type MyPoint (in Program
2.2-8, we used variables of type MyPoint3D). In this example, equals is executed in
MyPoint3D, if it is a virtual method, and in MyPoint, if not. getX is executed in MyPoint
as before.
Generally, for a virtual method the method dispatch always starts the search in the dynamic type
of the object, where the method has been invoked; if the method is not found there, it is searched
in the base class, and so on, until a class is found which implements the method. In the case of
non-virtual methods, the search starts in the scope of the static type of the object (the type at
compile-time, MyPoint). The same is valid, for example, if equals is called in getY
(implemented in MyPoint): if equals is a virtual method, it is executed in MyPoint3D (see
dark grey path in Figure 2.2-4); if not, it is executed in MyPoint.
Generally, all method calls to an object and within an object (method calls without explicit
specification of the receiver object), are delegated to the same unit “this” or “self”, no matter if
the methods are implemented in the object’s class or one of of its base classes. With delegation,
as the term is used in prototypical programming languages, Object, MyPoint, and
MyPoint3D can be different instances (rather than classes), and we would get the same
behavior (see Figure 2.2-4).
Note that delegation is not class inheritance. It is also not (only) forwarding; when a method is
invoked on an object, the call may be delegated from the object’s class to a base class which
implements the method (in the case that it is not directly implemented in the class of which the
object is an instance). In short, delegation is forwarding plus the concept of a common “self”.
2.2.5 Type-Safe Delegation and Dynamic Component Modification
Before we go on with an introduction of our new approach, let us take a look at the following
research project. Kniesel G. describes in [KNIES99] a concept for dynamic component
modification and delegation through object-based inheritance, and discusses its advantages and
issues. The concept was developed in the frame of the research project Darwin.
The goal of the project is described on the corresponding Internet pages as follows:
“The Darwin project aims to improve the foundation of object-oriented systems by
bridging the gap between the two families of object-oriented languages known today:
class-based and prototype-based ones.”
“The Darwin model describes typed, class-based inheritance extended by static and
dynamic object-based inheritance.”
The Darwin model is realized in the programming language Lava, an extension of Java. Its typesafe delegation concept and integration into a programming language provides the developer
with a high degree of usability through the possibility of static type-checking, while also
providing the flexibility of dynamic component modification.
16
2 Method Dispatch and Delegation
Dynamic component modification includes the customization of components such that they are
better suited for specific tasks. Kniesel proposes a classification of known approaches of
dynamic component modification according to
v their need for preexisting “hooks” in the application as either suitable for anticipated or
unanticipated changes.
v the time when a modification is made as either static, load-time or dynamic (runtime). We
assume that the reader is familiar with the meaning of these terms.
v their ability to adapt whole component types or individual component instances as either
global or selective. Selective approaches can be further classified as either replacing or
preserving, depending on whether they replace an existing component instance by its
modified version or let both be used simultaneously.
v the applied techniques as either code-modification-based, wrapper-based or meta-levelbased.
The need of unanticipated dynamic component modification has been repeatedly pointed out in
literature (e.g. [MAETZ97]). When developing a component, you may not always know which
hooks might be needed in the future by users of your component. Users of your component might
want to make enhancements to your component (without having the source code), as well as you
might want to make modifications to third-party components.
With global approaches, changes are applied to a component type rather than to its instances, so
the change affects every single instance of such a component. This may be comfortable, because
changes must be applied at one place only, but raises an evolution problem: some clients might
still require access to instances of the component using its “original” interface and semantics.
You cannot simply break their code. On the other hand, selective approaches also have their
shortcomings. For instance, the component to be replaced might not be prepared to hand over its
private data to its new version, if the modification was not anticipated. The application might
also require simultaneous use of the component’s original and new version.
Code-modification uses two inputs, a class to be modified and a specification of the
modifications. For instance, aspect-oriented programming uses this approach. Aspects are
“weaved” into functional code, i.e. code is generated from the original source code and aspects;
see Section 6.2.
Meta-level-based architectures allow manipulation of a system at runtime. Examples for metalevel-architectures are Smalltalk and the IBM System Object Model (SOM); see Sections 6.8 and
6.11. Kniesel argues in [KNIES99] on Page 3 that meta-level-architectures have two main
limitations: they defeat static type system and are inefficient.
When active components neither can be directly modified, nor unloaded from the system, we are
faced with the problem to change their behavior. One way to cope with this problem are
wrappers. Clients do not directly send messages to the original component, but to wrapper(s).
The wrappers may implement the corresponding functionality, or simply forward messages to the
original component.
With respect to this classification, the Darwin model presents an approach to unanticipated,
dynamic, selective, wrapper-based, object-preserving component modification. Like in Darwin,
our flexible method dispatch can be used for dynamic, selective, wrapper-based, objectpreserving component modification, when it is applied at instance-level (generic component
instance wrappers handle dynamic methods and forward calls to static ones). When integrated at
class-level like shown in Program 2.7-1, we use it rather globally and meta-level-based. The
concept is the same. The meta-level-approach has one major shortcoming: the programming
language or more precisely its reflection facilities have to be adapted. Reflection is the ability to
2.2 State of the Art
17
obtain information about the fields, constructors and methods of any class or component at
runtime. See [ENGLA97] on Pages 40-42 for a short introduction into Java reflection.
Now back to delegation. In contrast to code modification, dynamic component modification
based on delegation does not require the source code of the components to be adapted. It can be
deployed at runtime.
Many “simulations” of delegation have been proposed, either as language-specific idioms
[COPLI91] or general patterns [GAMMA95]. Simulation techniques and their drawbacks are
discussed and summarized in [HARRIS97] and [KNIES98].
The main disadvantages of the simulations are:
v the need to anticipate the use of a piece of software as part of a larger composite and to
provide “hooks” that allow the correct treatment of “this” in the context of the composite.
v the need to obey rigid coding convention to implement the hooks.
v the need to edit or at least recompile the wrapper class (the delegating class) when the
interface of the original class (delegated-to class) changes.
According to [KNIES99], each of the simulation techniques has additional shortcomings in terms
of limited applicability, limited functionality, limited reusability and excessive costs:
v Storing a reference to “this” or “self” in parent objects (or base classes) has limited
applicability. Sharing of one parent by multiple delegating children cannot be expressed at
all and recursive delegation can only be simulated with significant runtime and software
maintenance costs.
v Passing a reference to “this” as an argument of forwarded messages requires to extend the
interface of methods in parent objects, which might not be possible, if the parent object is
part of a ready-made, black-box component (a third-party component). Furthermore, the
typing of the explicit “this” argument interacts in subtle ways with the construction of
subclasses of parent classes. In the end, the simulation either does not reach full
functionality of delegation or it does so at the price of excessive costs for managing class
hierarchy changes, rendering reuse ad absurdum.
In Darwin / Lava, objects can delegate to others referenced by their delegation attributes. If a
class C declares a delegation attribute of type P, we say that C is a declared child class of type P
(and of its subtypes), and P is a declared parent class of C. A class can use the methods of its
declared parent classes as if they were declared in the same class, or in a base class.
Delegation attributes are declared in an object’s class by adding the keyword delegatee to an
instance variable declaration. There are two types of delegation attributes: mandatory and
optional. An attribute is called mandatory if it must have a non-null value; optional otherwise.
As example we use a class Shape. A Shape represents a visual object and holds the necessary
data structure for the object. Concrete examples are rectangles, polygons, images. Painters are
responsible for actually drawing Shape objects on a canvas in a graphical user-interface.
In this code fragment we declare Painter to be a declared parent class of class Shape. As
painter we instantiate a BorderPainter which paints a Shape object and its enclosing
border.
18
2 Method Dispatch and Delegation
Program 2.2-10 Declared parent
public class Shape {
// the delegatee "painter" must not be null
mandatory delegatee Painter painter;
public Shape() {
painter = new BorderPainter(); // draws the object and its border
}
// switch paint strategy
void setPainter(Painter painter) { this.painter = painter; }
Point getPos() {...}
}
The following picture illustrates how the classes are related to each other. We use the extended
UML-notation “delegates to”.
extended UML-notation:
C delegates to P
C
Shape
P
Painter
Point getExtent()
Point getPos()
draw()
Point getExtent()
SimplePainter
BorderPainter
draw() {
Point p=this.getPos();
Point e=this.getExtent();
...
}
...
Figure 2.2-5 Declared parent and child
Since Shape is a declared child class of Painter and its derived classes, the methods of
BorderPainter can be used as if they were declared in Shape or one of its base classes.
In the following case, we call the method draw on c, an instance of class Shape. The method
call will be delegated to p, an instance of class BorderPainter; draw in turn calls getPos
which is implemented in class Shape, so the method call is delegated back to c. In short, c and
p are united by a common “this”.
draw()
draw()
c: Shape
p: BorderPainter
this.getPos()
Figure 2.2-6 Delegation to declared parent
Note that if BorderPainter introduces methods that are neither declared in Painter (the
declared parent of Shape), nor in a base class Shape, e.g. an own getPos-method, then calls
to such methods are not delegated back to Shape instances (one would expect that a child
always overrides methods of the parent). This is a special behavior of the Darwin model and is
described in [KNIES99] on Page 11.
The Darwin model offers a certain level of flexibility while providing good performance and
type-safety. On the other hand, a shortcoming is the necessity to statically declare parents.
Possible future component enhancements must be anticipated; hooks like in Program 2.2-10
(mandatory delegatee Painter) are necessary.
Read more about the Darwin / Lava delegation mechanism and a summary of dynamic
component modification features and issues in [KNIES99] and the corresponding Internet pages.
2.3 Flexible Method Dispatch
19
2.3 Flexible Method Dispatch
The method look-up and invocation (also called method dispatch) can be optimized so that
methods of base classes need not be searched in the class hierarchy as necessary in Smalltalk
method dictionaries, while keeping method dispatch dynamic like in Smalltalk and unlike vtables.
Based on this idea we propose a flexible message dispatch mechanism and show examples for
the usage. The mechanism has been designed to be deployed on top of the reflection facilities of
common programming languages. We understand reflection as the ability to obtain information
about the fields, constructors and methods of any class or component.
Before we take a closer look at the dynamic capabilities of the flexible method dispatch, let us
see how the mechanism basically works and how it is realized in contrast to the mechanisms
described in the previous sections.
Figure 2.3-1 depicts the same object p and its classes as Figure 2.2-1, with one difference:
method references of a base class (e.g. Object) are also in the method dictionaries of derived
classes (MyPoint). Note that in Smalltalk each class has a method dictionary with direct
references to its methods. Methods of base classes are not stored in method dictionaries of
derived classes. In C++, v-table (virtual method tables) entries are direct pointers to the methods
of the class or its (direct or indirect) base classes. With the optimization shown in Figure 2.3-1, a
value in a method dictionary is a structure containing a direct reference to a method (getX) of
the same class and a list of indirect references to dictionary entries (hashCode) of other classes
(Object). The key is a method name. We call such a structure a method reference, respectively
a Dispatcher.
object p
p : MyPoint
class MyPoint
<<instance of>>
MyPoint
class Object
Object
<<derived from>>
<<derived from>>
nil
<<ref to class>>
<<ref to base class>>
<<ref to base class>>
x : int
...
...
y : int
<<dispatch>>
<<dispatch>>
<<dispatch>>
int hashCode()
int hashCode()
...
int equals(Object)
method
dictionary
<<dispatch>>
<<state change
notification>>
method
dictionary
int equals(Object)
...
int getX()
<<state change
notification>>
<<dispatch>>
setX(int)
int getY()
getX
method code
int hashCode()
method code
setY(int)
<<state change
notification>>
<<dispatch>>
executable methods
int getX()
int equals()
method
code
method code
executable methods
Figure 2.3-1 Method dispatch using indirect method references
This approach allows us to replace base classes and methods by others at runtime. In contrast to
v-tables no indexes must be recalculated in this case. Method references are notified whenever
the state (“Enabled”, “Disabled”, etc.) of an associated method changes. For such a notification,
the method references (for example, hashCode in MyPoint) need not be looked up, since they
are listeners of either the method object itself (method hashCode in Object), or an other
associated method reference (method reference hashCode in Object). Furthermore, the
20
2 Method Dispatch and Delegation
approach allows us to attach listeners also to method references (not only to methods), which are
notified with each method invocation or state change. Note that method states are a feature
introduced with this method dispatch mechanism. See also Section 4.3.7.
We can also use this mechanism for multicasts (broadcasts) by delegating method calls to a set of
Dispatchers (equivalent to .NET delegates). Like .NET delegates, our Dispatchers can reference
method objects of different classes and objects.
When a method is invoked, the method is searched by simply following the Dispatchers
(hashCode in class MyPoint) to a real method (method hashCode of class Object).
The flexible method invocation comprises the following steps. Compare to the Smalltalk method
dispatch algorithm in Section 2.2.1 (Pages 8f) and the v-table-based dispatch in Section 2.2.2
(Pages 9f):
1. Get the class object
(MyPoint) of the object (p) where the method (getX) was invoked
2. Get the method dictionary
a dictionary (key/value map) where the entries are references to methods and the keys are
the corresponding method names.
3. Look up the method reference (Dispatcher)
for getX in the Dispatcher dictionary of the current class
4. If found, then resolve the method reference
a) If the method reference is a real method, then invoke the method code (getX) and go to
step 6
b) If the method reference is an indirect reference to a method, then dereference it (take the
value which it references) and continue with step 4a
5. If not found then throw an error (the method does not exist) and go to step 6
6. Done
2.4 Method Dispatch Using Reflection
In the following illustrations, code fragments and explanations are based on the reflection
facilities of Java. One reason why we choose a reflection-based approach rather than a direct
integration of the flexible method dispatch mechanism into the Java programming language is
that we want to avoid confusion with code-fragments of the Java programming language (what is
part of the Java development kit and what is new?). Another reason is that this mechanism was
mainly designed for dynamic object-based use. More about that later.
To avoid confusion with the well-known class concept and its implementations, we introduce the
term Activity Set for sets of operations (see also Section 1.2). The concept is similar to the class
concept. Like a class, an Activity Set is a named scope and contains a dictionary (key/value map)
with references to operations. In our case, operations are called Activities, and reference objects
to Activities are called Dispatchers. Activity Sets can be nested (so they have a path) and can
acquire from (delegate to) others. The main difference to classes is that Activity Sets are based
on objects rather than on static type-information. Their Activities can be defined in different
objects. An Activity Set treats its Activities, respectively their associated objects, as unit, so it
supports delegation to independent objects. But more about that later.
In the following, we do not use the terms “base class” or “derived class” together with the
flexible method dispatch, but rather the terms “parent and “child”. Note that an Activity Set may
directly be related to a class.
2.4 Method Dispatch Using Reflection
21
To reflect that we choose a dynamic object-based approach rather than class inheritance, we
slightly adapt Figure 2.3-1.
<<caller>>
object p
Activity Set myPoint
p : MyPoint
myPoint: ActivitySet
Activity Set object
object: ActivitySet
<<acquires from>>
<<ref to class>>
<<ref to parent>>
<<ref to parent>>
x : int
...
...
<<acquires from>>
nil
y : int
<<dispatch>>
<<dispatch>>
<<dispatch>>
int hashCode()
int hashCode()
...
int equals(Object)
int equals(Object)
Dispatcher
dictionary
<<dispatch>>
<<state change
notification>>
Dispatcher
dictionary
...
int getX()
<<state change
notification>>
setX(int)
<<dispatch>>
int getY()
getX
method code
int hashCode()
Activity
setY(int)
<<state change
notification>>
<<dispatch>>
Activity
int getX()
int equals()
method
code
Activity
Activity
Figure 2.4-1 Method dispatch using Dispatchers and Activity Sets
There is not much difference to Figure 2.3-1—we use the classes MyPoint and Object, or
more precisely their Activity Set equivalents, for this example. We assume that the class
MyPoint is not statically derived from the class Object anymore. The corresponding Activity
Set myPoint is a child of object and “acquires” from it, that is it dynamically inherits from
object. So we get a dynamic relationship between these classes instead of the static one of
Figure 2.3-1.
The caller of an Activity (a “dynamic” method) makes an association between the involved
object and an Activity Set. More precisely, the Activity Set is initialized with the methods of the
object’s class; the Activity Set can then be used to dynamically invoke methods.
Activity Set myPoint3D
Dispatcher
dictionary
int hashCode()
...
Dispatcher
int equals(Object)
int equals(Object)
<<dispatch>>
Activity
int equals()
Activity Set myPoint
<<state change
notification>>
Dispatcher
Activity
int equals(Object)
int equals()
Activity Set object
Dispatcher
int equals(Object)
other Activity Sets
Dispatcher
int equals(Object)
Figure 2.4-2 Dispatcher chain
Activity
int equals()
22
2 Method Dispatch and Delegation
Figure 2.4-2 illustrates how Dispatchers and Activities are related to each other. An entry in a
Dispatcher dictionary—here that of Activity Set myPoint3D—is a reference to a Dispatcher in
the same Activity Set. Note that the Dispatcher dictionaries of the other Activity Sets are not
shown here. The Dispatcher may have a reference to an Activity of the same Activity Set and/or
references to Dispatchers of other Activity Sets (the parent Activity Sets in a delegation
relationship). The parent of myPoint3D is myPoint, and that of myPoint is object.
In the following we see the flexible method dispatch algorithm as pseudo code.
Program 2.4-1 Method look-up using (indirect) Dispatchers (pseudo code)
Object perform(ActivitySet a, Object p, String activityName, Object[] arguments) {
Dispatcher m = a.getDispatcher(activityName);
// get a dispatcher
if (m != null) {
while (!m.isActivity()) {
[1]
m = m.getDispatcher();
// get dispatcher target
}
[2]
return m.getActivity().perform(p, arguments); // invoke the retrieved
}
// activity
else {
... // throw an error (activity not found)
}
}
An Activity Set is passed to the perform-method. It contains Dispatchers (method references)
to the object p. Program 2.4-3 describes how an Activity Set can be generated from an object.
We assume that the class ActivitySet provides a method getDispatcher which returns a
(direct or indirect) reference to the Activity associated with the given Activity name
(activityName); the implementation of the class is shown later. Actually a Activity signature,
i.e. the Activity name including parameter and result types, must be specified for
getDispatcher, but for the simplicity of the example we ignore that for now. If the value
returned by getDispatcher is not null, we call getDispatcher until we get the actual
Activity [1]; otherwise we throw an error (Activity not found). Finally we execute the Activity
(getActivity().perform()) [2].
Note that the code fragment above is pseudo code and may not compile. However, there is a
conceptually equal implementation in the PCoC framework. The term Activity is used for method
objects in the context of PCoC; their Activity Sets are dynamic scopes where Dispatchers of
different objects can be added to; method reference objects containing the forwarding logic are
called Dispatchers. See Section 5.6. A more detailed description of Activity Sets is given later.
Program 2.4-2 illustrates a simple implementation of an Activity. When perform is called, the
argument types are checked and finally the template method doPerform is called. As
containers for arguments and return values we use instances of class Material. The
implementation of class Material is trivial.
See Program 4.3-5 for the corresponding implementation of an Activity in PCoC.
2.4 Method Dispatch Using Reflection
23
Program 2.4-2 A simple Activity implementation
class Activity {
Method performMethod;
String name;
Object provider;
Activity(Method m) {
name = m.getName();
performMethod = m;
provider = null;
// is set when added to an Activity Set
}
public Material perform(Object o, Material args) {
if (checkArguments(args)) {
// do a dynamic type check
return doPerform(o, args);
}
return null;
}
protected Material doPerform(Object o, Material args) {
Object result = null;
try { result = performMethod.invoke(o, args.toArray());
} catch ... // NoSuchMethodException, IllegalAccessException,...
return new Material(result);
}
...
}
As example we dynamically invoke the method hashCode of an instance of the class
MyPoint using an Activity Set. We assume that the method is exposed as Activity by the
parent Activity Set object, that is it is implemented in the associated class Object, and not
by myPoint:
Program 2.4-3 Invoking a method using Dispatchers (pseudo code)
...
MyPoint p = new MyPoint();
...
// use the methods of the object’s class to initialize an Activity Set
ActivitySet myPoint = PCCRegistry.getOrCreateActivitySet(p.getClass());
ActivitySet object = PCCRegistry.getOrCreateActivitySet(Object.class);
myPoint.acquire(object);
Object[] arguments = new Object[] {}; // no arguments
activityName = "hashCode";
Object result = perform(myPoint, p, activityName, arguments);
First, we retrieve the class of object p (where the specified method will be invoked) and use it for
the initialization of a new ActivitySet instance.
Note that such an Activity Set is generally created only once and reused many times. For that
reason, we create the Activity Set using the Activity Set registry (PCCRegistry). The registry
is a singleton and assures that there is only one Activity Set per class, respectively class name, in
the system. As already mentioned, we decided not to integrate the flexible method dispatch
approach into a programming language in order to be able to use conventional compilers. On the
other hand, if we integrated it into a programming language, classes would be capable of
generating and delivering corresponding Activity Sets. However, it may not make sense that each
class (or object) has an own Activity Set, and there may be Activity Sets which are not associated
with only a single class or object. An Activity Set may have method references (Dispatchers) to
several independent classes and/or objects.
The second Activity Set in our example corresponds to the methods of the class Object, and is
acquired by the myPoint Activity Set. The Activity Sets stay related to each other, unless we
call discard (the opposite of acquire).
24
2 Method Dispatch and Delegation
We use the method perform of Program 2.4-1 to invoke hashCode on the object p.
myPoint does not directly offer the method, therefore it has an indirect Dispatcher pointing to
the Dispatcher of hashCode in Activity Set object. The Activity and the associated method
are finally found and executed in object, respectively in the class Object. The reasons why
we use dynamic method invocation, i.e. method invocation based on reflection, rather than static
method invocation become clear when we go into detail.
But first, let us take another look at the method dispatch algorithm of Smalltalk and compare it to
the flexible method dispatch. We use Java-like pseudo code in order to make the comparison
easier:
Program 2.4-4 Method look-up algorithm of Smalltalk (pseudo code)
Object perform(Class c, Object p, String methodName, Object[] arguments) {
Method m;
Class pc = c;
while (pc != null) {
m = pc.getMethod(methodName); // get the method, if defined in the
// current class
if (m != null) {
[2]
return m.invoke(p, arguments); // invoke the retrieved method
}
[1]
pc=pc.getSuperclass();
// provided that there is only one base class
}
}
The main difference to the flexible method dispatch mechanism is that the requested method is
retrieved by searching it in the class hierarchy (see [1] in Program 2.4-4); in contrast to that the
flexible method dispatch retrieves a method by resolving indirect Dispatchers (see [1] in Program
2.4-1). More precisely, in the worst case Smalltalk does a lookup in the method dictionaries of
the class and each base class of the given object, whereas the flexible method dispatch does only
one lookup in the Dispatcher dictionary of the object’s Activity Set, respectively class, and
traverses the hierarchy by resolving (dereferencing) Dispatcher references.
Note that some Smalltalk systems may have slightly different implementations of this look-up
algorithm.
2.5 Dispatcher-Dictionary Implementation
A Dispatcher dictionary for Figure 2.4-1 could look like Program 2.5-1 (simplified).
In this Dispatcher dictionary class we define methods for adding and removing methods. When
an Activity or Dispatcher is added, a new Dispatcher is created as wrapper of the original one and
stored in the dictionary. Take a look at Program 2.4-1 to see how Dispatchers are resolved.
Note that, for example, in Java, a Method object (as needed for addActivity) of a class c
can be retrieved by calling c.getMethod("<methodName>", Class[]
parameterTypes). asInterfaceString, a method of PCCActivityInterface,
generates an interface specification string for the parameter type list of the given method. See
Sections 4.3.4 and 5.5.1 for descriptions of the dynamic Activity type concept and the
PCCActivityInterface class.
The implementations of asInterfaceString and methods of DispatcherDictionary
are omitted here, since they are not relevant to explain the concept.
2.5 Dispatcher-Dictionary Implementation
25
Program 2.5-1 Dispatcher dictionary (pseudo code)
public class DispatcherDictionary {
private HashMap dispDict;
public DispatcherDictionary() {
}
public void addActivity(Method m) { // add method and wrap it by a dispatcher
HashMap dsps = (HashMap)dispDict.get(m.getName());
if (dsps == null) {
dsps = new HashMap();
// create a hash map for methods
dispDict.put(m.getName(), dsps);
// with the same name
}
// build a signature from the parameter types and use it as key for
// the hash map
String signature = PCCActivityInterface.asInterfaceString(
m.getParameterTypes());
dsps.put(signature, new Dispatcher(new Activity(m)));
}
public void addDispatcher(Dispatcher d) {
// add Dispatcher and wrap
// it by another Dispatcher
... // the same as above, except last line
dsps.put(signature, new Dispatcher(d));
}
public Dispatcher removeDispatcher(String dispName, Class[] parameterTypes) {
HashMap dsps = (HashMap)dispDict.get(dispName);
// get method hash map
String signature = PCCActivityInterface.asInterfaceString(
parameterTypes);
// and remove the
if (dsps != null) {
// Dispatcher
Dispatcher m = (Dispatcher)dsps.remove(signature);
if (dsps.size() == 0) {
dispDict.remove(dispName);
}
return m;
}
return null;
}
public Dispatcher getDispatcher(
String dispName, Class[] parameterTypes) {
HashMap dsps = (HashMap)dispDict.get(dispName);
// get method ref
String signature = PCCActivityInterface.asInterfaceString(
parameterTypes);
// from the hash map
if (dsps != null) {
return (Dispatcher)dsps.get(signature);
}
return null;
}
public Dispatcher[] getDispatchers(
// get all Dispatchers
String dispName) {
// with name dispName
HashMap dsps = (HashMap)dispDict.get(dispName);
if (dsps != null) {
return dsps.values().toArray();
// returns a set of Dispatchers
}
return null;
}
public Dispatcher[] getDispatchers() { // get all Dispatchers
... // returns a set of Dispatchers
}
public String makeSignature(Class[] parameterTypes) {
... // concatenate class names
}
...
}
2.6 Dispatcher Implementation
For the following code fragments we assume that methods are always represented by instances of
the class Activity and that they are subject to be explicitly used by the developer (not only
implicitly by the reflection system of the programming language).
26
2 Method Dispatch and Delegation
The corresponding Dispatcher class could look like Program 2.6-1.
Program 2.6-1 Dispatcher implementation (pseudo code)
public class Dispatcher {
private Activity a;
private Dispatcher dsp;
// for simplicity of this example only one acquired
// dispatcher; normally this is an object array
// (for multicasts)
public Dispatcher(Dispatcher dsp) {
this.dsp = dsp;
a = null;
}
public Dispatcher(Activity a) {
this.a = a;
dsp = null;
}
public boolean isActivity() { return dsp == null; }
public Dispatcher getDispatcher() { return dsp; }
public void addDispatcher(Dispatcher dsp) { this.dsp = dsp; }
public Activity getActivity() { return a; }
public void setActivity(Activity a) { this.a = a; }
public String getName() {
if (a != null) {
return a.getName();
} else if (dsp != null) {
return dsp.getName();
}
return "";
}
public setName(String name) {
if (a != null) {
return a.setName(name);
} else if (dsp != null) {
return dsp.setName(name);
}
}
Object perform(Object p, Object[] arguments) {
if (isActivity()) {
return getActivity().perform(p, arguments);// invoke retrieved Activity
} else {
return getDispatcher().perform(p, arguments); // get Dispatcher target
}
}
...
}
The class Dispatcher supports direct references to Activities and indirect ones (references to
other Dispatchers). Dispatchers can be linked in chains (new Dispatcher(dsp)), whereas
the ends of such chains are always direct references to Activities.
Note that we have two member variables—a reference to an Activity and one to another
Dispatcher. For multiple delegation parents you may introduce an array of Dispatchers and
extend or shrink it on demand.
Dispatcher provides a method perform which corresponds to the method dispatch
algorithm of Program 2.4-1, but as recursive implementation. The Dispatcher for a given Activity
name and object p is retrieved somewhere else:
2.6 Dispatcher Implementation
27
Program 2.6-2 Retrieving a Dispatcher (pseudo code)
Class pCl = p.getClass();
// or MyPoint.class
Class[] parameterTypes = new Class[] {Object.class};
Object[] arguments = new Object[] {};
ActivitySet myPoint = PCCRegistry.getOrCreateActivitySet(pCl);
Dispatcher dsp = myPoint.getDispatcher("getX", parameterTypes);
Integer result = (Integer)dsp.perform(p, arguments);
When the caller does not need (or want) to keep Dispatchers for later use, there is a simpler way
for invoking Activities, respectively their associated methods:
Program 2.6-3 Dynamically invoking a method (pseudo code)
ActivitySet myPoint = PCCRegistry.getOrCreateActivitySet(p);
Integer result = (Integer)myPoint.perform(p, "getX", arguments);
This assumes that the class ActivitySet provides a few convenience methods. For instance,
there is a method that allows us to specify an object instead of a class when creating an Activity
Set. The method perform of class ActivitySet may automatically determine the signature
of the requested method. It can do so by looking for a Dispatcher in the Dispatcher dictionary,
whose signature matches the given method name and the types of the given arguments. If there
are ambiguities, an error could be thrown. Note that such type-checks are performed at runtime,
therefore errors can also be reported only at runtime.
The Activity Set is normally reused many times, so it may be stored as member variable in a
class.
2.7 Using Dispatcher Dictionaries and Indirect References
Now, let us take a look at the implementation of the class ActivitySet and how it
collaborates with its Dispatcher dictionary:
Program 2.7-1 Class ActivitySet
public final class ActivitySet {
private DispatcherDictionary dd;
private ActivitySet parent;
ActivitySet(Class cl) {
// can only be created by the Activity Set registry
md = new DispatcherDictionary();
if (cl != null) {
int i;
Method[] methods = cl.getMethods();
for (i=0; i<methods.length; i++) { // add all methods of the given class
addActivity(methods[i]);
}
parent = PCCRegistry.getOrCreateActivitySet(cl.getSuperclass());
acquire(parent);
}
...
}
...
The Activity Set constructors take either a class as argument, an object, or a name (which may
correspond to a class name). In all cases the methods of the given class are exposed as Activities
and added to the Activity Set at creation time. If there is a base class, a corresponding parent
Activity Set is created and acquired, that is its Dispatchers are wrapped and added to this Activity
Set (iterate over all Dispatchers of the parent and add it by using addDispatcher).
28
2 Method Dispatch and Delegation
Program 2.7-2 Class ActivitySet (2)
...
ActivitySet(Object o) {
// can only be created by the Activity Set registry
this(o.getClass());
// add all methods of the object’s class
}
ActivitySet(String name) { // can only be created by the Activity Set registry
this(Class.forName(name)); // add all methods of the class
}
...
public void addActivity(Method m) { // creates an Activity from the given
dd.addActivity(m);
// method object
}
public void addDispatcher(Dispatcher dsp) {
dd.addDispatcher(dsp);
}
public void removeDispatcher(String dispName, Class[] parameterTypes) {
dd.removeDispatcher(dispName, parameterTypes);
}
public Dispatcher getDispatcher(
String dispName, Class[] parameterTypes) {
return dd.getDispatcher(dispName, parameterTypes);
}
public Method[] getDispatchers() {
return dd.getDispatchers();
}
...
We use the method getMethods of the Java reflection package to get all methods of the given
class. Generally, Activities and Dispatchers can be added or removed at any time. For the
retrieval of a single or all Dispatchers we can use the methods getDispatcher, respectively
getDispatchers.
Note that we use the Dispatcher dictionary class of Program 2.5-1 and the Dispatcher class of
Program 2.6-1.
Program 2.7-3 Implementation of perform in class ActivitySet (pseudo code)
public Object perform(Object p, String dispName, Object[] arguments) {
int i=0;
// get all Dispatchers with the given name
Dispatcher[] dsps = dd.getDispatchers(dispName);
while (i < dsps.length) {
// iterate over all Dispatchers
Class[] parameterTypes = dsps[i].getParameterTypes();
if (parameterTypes.length() == arguments.length) {
int j=0;
// search for a Dispatchers with a
while (j<arguments.length) { // matching interface; the parameter
// types must match the types of the
// given arguments (actual parameters)
if (!parameterTypes[j].isInstance(arguments[j])) { break; }
j++;
}
// found a matching Dispatcher; invoke it
if (j >= arguments.length) { return dsps[i].perform(p, arguments); }
}
i++;
}
... // throw error
return null;
}
...
}
The implementation of the perform method is shown above. It retrieves a Dispatcher with a
matching interface from the Activity Set’s Dispatcher dictionary.
2.7 Using Dispatcher Dictionaries and Indirect References
29
The Dispatcher dictionary dd returns an array of Dispatchers which match the given Dispatcher
name. The array is searched for a Dispatcher whose parameter types match the types of the
arguments (actual parameters) passed to this method (perform). If there is a matching
Dispatcher, it is invoked, and thus its associated Activity; otherwise an error is thrown.
Now let us make use of the dynamic method capabilities of the flexible method dispatch:
Program 2.7-4 Adding indirect Dispatchers to a class (pseudo code)
ActivitySet myPoint = PCCRegistry.getOrCreateActivitySet(MyPoint.class);
ActivitySet object = PCCRegistry.getOrCreateActivitySet(Object.class);
myPoint.acquire(object);
Class[] parameterTypes = new Class[] {Integer.class, Integer.class};
myPoint.addActivity(
// add new method
"setXY"
new Activity("setXY", parameterTypes) {
public Object doPerform(Object obj, Object[] args) { // executed when the
setX((Integer)args[0]);
// Activity is invoked
setY((Integer)args[1]);
return null; // the actual functionality
}
...
});
First, we create an Activity Set from the class MyPoint. Remember, an Activity Set adds
Activities and Dispatchers for all methods of the specified class to its Dispatcher dictionary at
creation time. In the given example all MyPoint methods are added as Activities and
Dispatchers to the Activity Set myPoint. After that, we explicitly add an Activity (method
object) setXY. The Activity may contain functional code in its (static) method doPerform, or
simply forward calls to the corresponding methods of the object obj. In the example above, the
functional code is directly implemented in the Activity’s method doPerform.
The implementation of addMethod can be found in Program 2.5-1.
2.8 Dynamically Adding Aspects to Methods
We may want to wrap existing methods in order to add aspects. An aspect is specific code to
validate actual parameters, to trace calls, etc. The following example should illustrate why
flexible method dictionaries (Dispatcher dictionaries) are useful for this purpose. We add aspects
before and after the original method equals.
For illustration of this example, we use Figure 2.3-1 and adapt it accordingly. See Figure 2.8-1.
eq_aspect_in and eq_aspect_out are the aspects we want to add. They are implemented
as Activities which should be called before and after the original Activity equals is invoked.
30
2 Method Dispatch and Delegation
<<caller>>
object p
Activity Set myPoint
p : MyPoint
myPoint: ActivitySet
Activity Set object
object: ActivitySet
<<acquires from>>
<<ref to class>>
<<ref to parent>>
<<ref to parent>>
x : int
...
...
<<acquires from>>
nil
y : int
<<dispatch>>
<<dispatch>>
<<dispatch>>
int hashCode()
eq_aspect_in
<<adds aspect>>
eq_aspect_out
Dispatcher
dictionary
int hashCode()
...
int equals(Object)
<<dispatch>>
<<state change
notification>>
int equals(Object)
Dispatcher
dictionary
...
int getX()
<<state change
notification>>
<<dispatch>>
setX(int)
int getY()
getX
method code
int hashCode()
Activity
setY(int)
<<state change
notification>>
<<dispatch>>
Activities
int getX()
int equals()
method
code
Activity
Activities
Figure 2.8-1 Dynamically adding aspects
The following code fragment shows the implementation:
Program 2.8-1 Adding aspects to Dispatchers (pseudo code)
ActivitySet myPoint = PCCRegistry.getOrCreateActivitySet(MyPoint.class);
ActivitySet object = PCCRegistry.getOrCreateActivitySet(Object.class);
myPoint.acquire(object);
Dispatcher dsp = myPoint.removeDispatcher("equals", parameterTypes);
dsp.setName("equals_orig");
myPoint.addDispatcher(dsp); // add the original "equals"-Dispatcher as
// "equals_orig"
myPoint.addActivity(
// add new Activity and Dispatcher "equals",
// including new aspects
new Activity("equals", parameterTypes) {
public Object doPerform(Object obj, Object[] args) {
ActivitySet as = PCCRegistry.getActivitySet(obj);
as.getDispatcher("eq_aspect_in").perform(obj, args);
Object ret = as.getDispatcher(
"equals_orig").perform(obj, args);
as.getDispatcher("eq_aspect_out").perform(obj, args);
}
...
}
);
We use the equals method of the class MyPoint for our example. The method is exposed as
Activity, respectively as Dispatcher. To wrap our original Dispatcher, we remove it, rename it to
equals_orig and add it again with the new name. Now the original Dispatcher name
equals is not used any more, so we can use it for our own Dispatcher wrapping the original
one. In the body of method doPerform we call eq_aspect_in, then the original equalsDispatcher equals_orig, and then eq_aspect_out. Note that the implementations of
eq_aspect_in and eq_aspect_out do not give any new insight into the discussed
concept, therefore they are not shown here.
2.8 Dynamically Adding Aspects to Methods
31
After having replaced the original equals Dispatcher with a wrapper, whenever client code is
using a Dispatcher with this name, our new Dispatcher, including the aspects eq_aspect_in
and eq_aspect_out, is used. Client code needs no change; existing calls to the Dispatcher
equals can stay syntactically the same, but lead to the invocation of the wrapper Activity rather
than the original Activity.
2.9 Notifications
Another feature of the introduced method dispatch mechanism is the ability to attach listeners to
Dispatchers. Listeners are used when additional operations should be executed before or after the
execution of a Dispatcher, or when an Activity or Dispatcher is added or removed or their states
change. In contrast to aspects as described above, notifications cannot be used to override the
behavior of the original Dispatcher. The actual Dispatcher and its associated Activity are
executed in any way. To illustrate this capability, we enhance Program 2.6-1 by a notification
mechanism.
Program 2.9-1 Dispatchers with notifications (pseudo code)
public class Dispatcher {
Object listeners;
...
Object perform(Object p, Object[] arguments) {
Object result;
notifyListenersBeforePerform(p, arguments);
if (isActivity()) { // if there is an Activity associated, invoke it
result = getActivity().perform(p, arguments);
}
else if (getDispatcher() != null) { // otherwise use an attached Dispatcher
result = getDispatcher().perform(p, arguments);
}
notifyListenersAfterPerform(p, arguments, result);
}
void addListener(DispatcherListener listener) {
if (listeners == null) { listeners = new ArrayList(); }
((ArrayList)listeners).add(listener);
}
void notifyListenersBeforePerform(Object p, Object[] arguments) {
ArrayList l = ((ArrayList)listeners);
if (l == null) { return; }
for (int i=0; i<l.size(); i++) {
((DispatcherListener)l.get(i)).beforePerform(p, arguments);
}
}
void notifyListenersAfterPerform(Object p, Object[] arguments, Object result) {
ArrayList l = ((ArrayList)listeners);
if (l == null) { return; }
for (int i=0; i<l.size(); i++) {
((DispatcherListener )l.get(i)).afterPerform(p, arguments, result);
}
}
...
The also modify the methods for adding and removing Activities and Dispatchers.
32
2 Method Dispatch and Delegation
Program 2.9-2 Dispatchers with notifications (pseudo code)
public void addDispatcher(Dispatcher dsp) {
this.dsp = dsp;
notifyListenersActivityAdded(dsp);
}
public void removeDispatcher(Dispatcher dsp) {
this.dsp = null;
notifyListenersActivityRemoved(dsp);
}
public void setActivity(Activity a) {
notifyListenersActivityRemoved(this.a);
this.a = a;
notifyListenersActivityAdded(a);
}
}
notifyListenersActivityAdded and notifyListenersActivityRemoved are
implemented equivalently to notifyListenersBeforePerform and
notifyListenersAfterPerform. We do not show the implementation in detail, since it
does not add relevant information to this example.
The corresponding listener interface may look as follows:
Program 2.9-3 Dispatcher listener interface (pseudo code)
public interface DispatcherListener {
void beforePerform(Object p, Object[] arguments);
void afterPerform(Object p, Object[] arguments, Object result);
void activityAdded(AbstractActivity a);
// AbstractActivity is the base
void activityRemoved(AbstractActivity a); // class of Dispatcher and Activity
...
}
The
relevant
statements
in
Program
2.9-1
are
the
method
calls
notifyListenersBeforePerform and notifyListenersAfterPerform. Each
Dispatcher manages a list of listeners (also called observers), and notifies with these methods
the listeners whenever the Dispatcher is invoked, i.e. whenever the method perform is called.
Note that listeners attach to specific Dispatchers and not directly to methods. This gives us the
flexibility to get notifications of invoked Dispatchers only for specific children of an Activity Set
providing the corresponding method. For example, we may want to be notified whenever the
method hashCode is invoked on the Activity Set myPoint, but we do not want to be notified
when hashCode is invoked on other children of the Activity Set object or on object itself.
Remember, the method hashCode is implemented in the class Object and added to the
corresponding Activity Set, therefore all children of object dynamically “inherit” the
Dispatcher.
The following code fragment illustrates how to make use of the notification capability. First, we
create an instance of MyPoint. After that, we retrieve a Dispatcher for method hashCode in
class MyPoint. Then we attach a listener to the Dispatcher and invoke the Dispatcher on object
p. The listener will be notified before and after the Dispatcher is invoked, respectively its
associated method hashCode of object p.
2.9 Notifications
33
Program 2.9-4 Using Dispatcher notifications (pseudo code)
public void foo() {
MyPoint p = new MyPoint(10,20);
ActivitySet myPoint = PCCRegistry.getOrCreateActivitySet(p);
ActivitySet object = PCCRegistry.getOrCreateActivitySet(Object.class);
myPoint.acquire(object);
Dispatcher dsp = myPoint.getDispatcher("hashCode", new Class[] {});
dsp.addListener(new MyListener());
int hc = ((Integer)dsp.perform(p, "hashCode", new Object[] {})).intValue();
}
public class MyListener implements DispatcherListener {
beforePerform(Object p, Object[] arguments) { ... }
afterPerform(Object p, Object[] arguments, Object result) { ... }
...
}
This example is quite simple. There is only one place where the Dispatcher hashCode is
invoked and and we know which arguments are passed. In this example, we actually need no
notification. We can modify the code directly, for example, to trace the method call. In real
applications it is more usual that there are many code fragments where a specific method is
called. As users of third-party libraries or frameworks, we might want to be able to hook into
methods of library or framework classes to trace method calls, trigger related operations, or to
cancel method calls, but we do not have the source code and therefore cannot directly modify it,
respectively, we should not do that for the sake of encapsulation. As library or framework
developers we may want to provide such hooks rather than allow clients to modify our source
code.
Note that with the PCoC framework, this notification mechanism is not directly provided by the
Dispatcher class, but by a base class. See Section 4.3.2.
2.10 Flexible Delegation
So far, we have got insight into the most common facilities for method and message dispatch:
method dictionaries, v-tables, and message interpreters. We have learned that delegation is a
dispatch mechanism to share behavior between objects. We know that delegation may be used
together with any of the dispatch facilities described above. In Section 2.2.5 we finally read about
the Darwin model—a mechanism for type-safe delegation and dynamic component modification
([KNIES99]).
With a flexible method dispatch, we propose a more drastic approach than the Darwin model in
terms of flexibility. It offers much flexibility (method objects and components can be added and
removed entirely at runtime like in Smalltalk), no static declarations of parents are necessary, and
“standard” compilers can be used (no proprietary compilers are necessary). Like Darwin, this
mechanism can be integrated into an existing project (at least, if the flexible-method-dispatch
framework has been implemented in the corresponding programming language) with little effort.
Client code that needs to use the flexible method dispatch, must invoke methods via the
perform-method of Activity Sets, instead of directly calling methods on the corresponding
classes or objects. Known drawbacks are the worse performance compared to the static
declaration system of Darwin, and the missing type-safety at compile time (type-safety is assured
at runtime only).
The Dispatchers we first encountered in Section 2.3 can be enhanced to pass the receiver Activity
Set (where a method is invoked) as argument to the dispatch method perform. The Activity Set
is passed on to the actual method which can use it for subsequent method calls. This concept
makes delegation possible across independent objects. We say it is a simulated
delegation—Activity Sets are not integrated in a programming language and therefore must be
passed explicitly when invoking Activities (no implicit “self” parameter; see below).
34
2 Method Dispatch and Delegation
Compare the following implementation of the perform method with that of Program 2.6-1.
Program 2.10-1 Dispatchers with receiver context (pseudo code)
public class Dispatcher {
...
Object perform(ActivitySet context, Object[] arguments) {
if (isActivity()) {
// invoke retrieved method
return getActivity().perform(context, arguments);
}
else {
// get method ref target
return getDispatcher().perform(context, arguments);
}
}
...
}
The perform method passes on the (receiver) Activity Set from Dispatcher to Dispatcher and
finally to a concrete Activity.
The corresponding modification in the perform method of the Activity Set class is illustrated
below (compare to Program 2.7-3).
Program 2.10-2 Dispatchers with receiver context (pseudo code)
public final class ActivitySet {
...
Object perform(String dispName, Object[] arguments) {
int i=0;
// get all Dispatchers with the given name
Dispatcher[] dsps = dd.getDispatchers(dispName);
while (i < dsps.length) {
// iterate over all Dispatchers
Class[] parameterTypes = dsps[i].getParameterTypes();
if (parameterTypes.length() == arguments.length) {
int j=0;
// search for a Dispatcher with a
while (j<arguments.length) { // matching interface; the parameter types
// must match the types of the given
// arguments (actual parameters)
if (!parameterTypes[j].isInstance(arguments[j])) {
break;
}
j++;
}
if (j >= arguments.length) { // found a matching Dispatcher; invoke it
return dsps[i].perform(this, arguments);
}
}
i++;
}
... // throw error
return null;
}
...
}
Basically this implementation is very similar to Program 2.7-3. We do not pass the receiver
object as first argument any more. Instead, with this implementation the Activity Set passes itself
as argument to the corresponding Dispatcher. Passing the Activity Set instead of the original
object may not make sense if all Dispatchers and Activities come from the same Activity Set, but
it does if some Activities are associated with different Activity Sets. This is similar to the
invocation of an instance method of a class, where we (implicitly) pass the receiver object
(“this”) as argument, but the invoked methods may be defined in the class or any of the direct or
indirect base classes of the object.
When a method is invoked, the method call must be delegated to the corresponding class of the
object where it has been invoked. If no receiver is specified explicitly, the current object (“this”)
2.10 Flexible Delegation
35
is used. All object-oriented programming languages support this “class-based delegation” by
means of the implicit “this” parameter.
For dynamic component modification it makes sense that objects of independent classes can also
be combined to units, quite like an object represents a unit combining the behavior of its class
and its base classes. There is only Lava which has a built-in object-based delegation mechanism
in addition to class-based delegation. Other approaches simulate object-based delegation by, for
example, using hooks or storing references in the parent objects of delegation relations.
Simulation techniques and their drawbacks are discussed and summarized in [HARRIS97] and
[KNIES98]. [KNIES99] summarizes disadvantages of simulation techniques (Pages 6f).
The mandatory static declaration of parents in Lava (see the beginning of this section) may not be
flexible enough for some cases, since it requires anticipation for where a hook might be needed.
This leads us back to our delegation approach.
Now that the Activity Set where a Dispatcher is invoked (the receiver) is passed to the Activity,
we can invoke other Dispatchers on the same Activity Set from within that Activity. In this case
the Activity Set has the same meaning for dynamic method calls across different objects as the
implicit parameter “this” has for static method calls across different classes.
Program 2.10-3 Delegation across different objects (pseudo code)
// add all methods of MyPoint to a new Activity Set: setX, setY, getX,...
MyPoint p = new MyPoint(); // a point object
Integer z = new Integer(17); // the z-value which extends p to a 3D point
ActivitySet a = PCCRegistry.getOrCreateActivitySet(p);
a.addActivity(
// add new method "setZ"
new BoundActivity("setZ", new Class[] {Integer.class}, z) {
public Object doPerform(ActivitySet a,
Object[] args)
// executed when the method
{
// is called
((Integer)obj).set((Integer)args[0]);
return null;
}
...
});
a.addActivity(
// add new method "getZ"
new BoundActivity("getZ", new Class[] {}, z) {
public Object doPerform(ActivitySet a,
Object[] args)
// executed when the method
{
// is called
return obj;
}
...
});
a.addActivity(
// add new method "setXYZ"
new BoundActivity("setXYZ",
new Class[] {Integer.class, Integer.class, Integer.class},
z) {
public Object doPerform(ActivitySet a,
Object[] args)
// executed when the method
{
// is called
a.perform("setX", new Object[] {args[0]});
a.perform("setY", new Object[] {args[1]});
((Integer)obj).set((Integer)args[2]);
return null;
}
...
});
a.perform("setZ", new Object[] {10});
Integer result = (Integer)a.perform("getZ", new Object[] {});
a.perform("setXYZ", new Object[] {10, 20, 30});
36
2 Method Dispatch and Delegation
In this example, we extend an Activity Set by an Activity from another Activity Set in order to
show the value of the flexible method dispatch. As basis for this example we use class MyPoint
of Program 2.2-5 (Page 14), and the Activity Set client code of Program 2.7-4 (Page 29).
In this example we introduce our own Activity-class BoundActivity. As opposed to
instances of class Activity, a bound Activity is associated with a particular object. This object
is passed as additional argument (z) to the BoundActivity constructor and is stored as
member variable obj. The method getOrCreateActivitySet creates an Activity Set and
initializes it with Activities for the methods of p. Instances of BoundActivity are created and
added to the Activity Set for each method of p, as opposed to Program 2.7-1 where instances of
class Activity were added.
We explicitly add the methods setZ, getZ, and setXYZ to the Activity Set a. Although point
p has only two dimensions (x, y) and the z-parameter is stored in a separate object, the
associated Activity Set a can be used as 3D-point. We say, it simulates a 3D-point. Dispatcher
calls are either delegated to Activities associated with the original object p (setX, setY) or to a
bound Activity added explicitly to the Activity Set (setZ).
As long as our objects or components are used through Activity Sets, we or users of our
components have the flexibility to modify the functionality at runtime without the need to
recompile and reload the corresponding client code.
Note that we used an argument array in this example, but in a concrete implementation of this
concept we may rather use an argument list class which supports named parameters. In addition
to that, we can introduce convenience methods to make the use of the flexible method dispatch
easier.
You find more information about delegation and its integration in the framework introduced in
this thesis in Section 4.6. Also relevant are Sections 5.4 and 6.1.
2.11 Prioritized Flexible Method Dispatch
Now let us take a closer look at parent/child-relationships between Activity Sets. We know that
an Activity Set can acquire (dynamically inherit from) other Activity Sets, just like a class can
inherit from other classes.
*
<<parent>>
acquire
:ActivitySet
* <<child>>
Figure 2.11-1 Parent/child relationship
This relationship can be used to switch between different implementations of the same kind of
service. This includes particular operations or whole objects or components.
To illustrate this idea, we take the example of Figure 2.2-5 and adapt it slightly. Instead of
classes and their instances, we use the corresponding Activity Sets. We assume that the class
Shape associated with the Activity Set shape contains only the data structure for a particular
shape. It has methods for setting and getting its position, extent, etc., but no method for
displaying the shape on the screen.
2.11 Prioritized Flexible Method Dispatch
37
list of parents
(acquired Activity Sets)
shape: ActivitySet
Point getPos()
Point getExtent()
...
:ArrayList
0
1
2
:
n-1
list of parents
(acquired Activity Sets
simplePainter: ActivitySet
void draw()
Point getExtent()
borderPainter: ActivitySet
void draw()
Point getExtent()
converters: ActivitySet
boolean exportTo(...)
...
:ArrayList
0
:
n-1
:ArrayList
0
:
n-1
:ArrayList
0
:
n-1
Figure 2.11-2 Prioritized acquisition
Using Activity Sets, we can dynamically enhance shape by additional functionality. We may
provide several painters to display Shape objects on the screen. Each painter implements the
method draw which does the actual job. We let the Activity Set shape acquire our painters,
respectively their methods. Additionally we acquire a converter which provides functionality to
export the shape to a graphics file.
Note that an acquisition relationship allows multiple parents, therefore a call of acquire adds a
new parent, and does not replace a previously acquired parent.
Program 2.11-1 Acquisition of Activity Sets (pseudo code)
ActivitySet shape = PCCRegistry.getOrCreateActivitySet(new Shape());
ActivitySet simplePainter = PCCRegistry.getOrCreateActivitySet(
new SimplePainter());
ActivitySet borderPainter = PCCRegistry.getOrCreateActivitySet(
new BorderPainter());
ActivitySet converter = PCCRegistry.getOrCreateActivitySet(
new Converter());
shape.acquire(simplePainter);
shape.acquire(borderPainter);
shape.acquire(converter);
For each Dispatcher of the acquired Activity Sets (parents), a Dispatcher is added to shape. We
can then invoke, for example, the Dispatchers draw and exportTo on shape, even if the
class Shape does not provide the corresponding methods. When such a Dispatcher is invoked,
the call is delegated, following the corresponding Dispatchers to the Activity Set which has the
actual (bound) Activity. Finally the static method associated with the bound Activity is invoked
on the corresponding object.
In our case, we have two painters and therefore we get a name clash with the draw Dispatchers.
We could remove one painter, but we assume that one painter reuses Dispatchers or more
precisely the associated Activity and static method of the other, and therefore both are required.
To cope with this problem, the parents, respectively their Dispatchers, are ranked. The ranking of
a parent is determined by its position in the list of parents. We say, Activity Sets with a lower
index have a higher priority.
In order to give borderPainter, respectively its Dispatchers a higher priority in shape, we
can use any of the following statements:
Program 2.11-2 Setting the focus to borderPainter (pseudo code)
shape.moveTo(borderPainter, 0);
borderPainter.setFocus();
38
2 Method Dispatch and Delegation
Both statements lead to a new ranking in the list of parents of the Activity Set shape. In contrast
to moveTo, a call of setFocus gives borderPainter the highest ranking in all its
(acquisition) children, and not only in shape.
list of parents
(acquired Activity Sets)
shape: ActivitySet
Point getPos()
Point getExtent()
...
list of parents
(acquired Activity Sets
:ArrayList
0
1
2
simplePainter: ActivitySet
void draw()
Point getExtent()
borderPainter: ActivitySet
:
n-1
void draw()
Point getExtent()
converters: ActivitySet
boolean exportTo(...)
...
:ArrayList
0
:
n-1
:ArrayList
0
:
n-1
:ArrayList
0
:
n-1
Figure 2.11-3 Prioritized acquisition (2)
When we invoke the Dispatcher draw on shape, the call is delegated to borderPainter
rather than to simplePainter now. When we invoke the Dispatchers exportTo, the call is
delegated to converter as before, since none of the higher priority Activity Sets provide the
corresponding Activity.
For broadcasts, that is when requests are delegated to two or more Activity Sets, the order in
which the actual methods are executed is determined by the ranking of the Activity Sets in the
list of parents. In order to support broadcasts, we have to make some enhancements to the
Dispatcher class.
Program 2.11-3 Dispatcher implementation (pseudo code)
public class Dispatcher {
...
Object broadcast(ActivitySet a, Object[] arguments) {
if (isActivity()) {
return getActivity().perform(p, arguments); // invoke retrieved Activity
}
else {
int i;
for (i=0; i < dsps.size(); i++) {
((Dispatcher)dsps.get(i)).perform(p, arguments);
}
return null;
}
}
...
private Activity a;
private ArrayList dsps;
}
The most relevant changes are the list of (parent) Dispatchers instead of a single Dispatcher, and
the new broadcast method which delegates to each Dispatcher in the list (cf. Program 2.6-1).
2.12 Containment Hierarchy
Besides the possibility of ordered acquisition (dynamic inheritance) and delegation, we may need
to group Activity Sets, like classes can be grouped through name spaces. For that reason, Activity
Sets have a path instead of only a name. See also Section 5.3.
2.12 Containment Hierarchy
shapePainters: Activ itySet
39
textPainters: Activ itySet
sim plePainter: A ctivitySet
v oid draw ()
Point getExtent()
borderPainter: Activ itySet
v oid draw ()
Point getExtent()
sim plePainter: A ctivitySet
v oid draw ()
Point getExtent()
borderPainter: Activ itySet
v oid draw ()
Point getExtent()
highlig hter: ActivitySet
v oid draw ()
Point getExtent()
Figure 2.12-1 Activity Set hierarchy
In this example, we have two Activity Set groups: shapePainters and textPainters.
The corresponding paths are “|ShapePainters” and “|TextPainters”. We use character
“|” as path delimiter to avoid confusion with file paths (“/”, “\”) or operators “.” and “->”. The
group shapePainters contains the two Activity Sets simplePainter and
borderPainter. The group textPainters contains the Activity Sets highlighter,
simplePainter and borderPainter. The latter two are identically named but different
from those of shapePainters.
The given Activity Set tree results in following Activity Set paths: “|ShapePainters”,
“|ShapePainters|SimplePainter”,
“|ShapePainters|BorderPainter”,
“|TextPainters”, “|TextPainters|Highlighter”, etc.
The path of an Activity Set can be set as follows:
Program 2.12-1 Setting the path (pseudo code)
borderPainter.setPath("|ShapePainters|BorderPainter");
borderPainter.setContainer(shapePainters);
Both statements are equivalent. shapePainters becomes the container of
borderPainter. Once created, the Activity Set can be retrieved from the registry as follows:
Program 2.12-2 Setting the path (pseudo code)
ActivitySet borderPainter =
PCCRegistry.getActivitySet("|ShapePainters|BorderPainter");
Activity Set groups have only one purpose: to distinguish between semantically similar Activity
Sets with the same name, but different behavior. They are ordinary Activity Sets and therefore
they can also acquire their nested Activity Sets (such with subpaths), which we call elements.
However, by default, containers do not automatically acquire their elements. They may provide
their own Activities. The path is stored as member of the ActivitySet class. We use the
terms container and elements for containment relationships, in order to avoid confusion with the
terms parent and children used in delegation relationships.
2.13 Remarks
So far, we have seen how method dictionaries (Smalltalk) and v-tables (C++/Java) work, what
delegation is and how it can be realized. We also learned about the Darwin model, its delegation
concept, and its realization in the programming language Lava.
We have introduced a new message dispatch mechanism that gives a certain level of flexibility,
but requires a different way of method invocation (more precisely, dynamic method invocation)
40
2 Method Dispatch and Delegation
compared to ordinary method calls. To leave existing code unchanged, a code generation tool
could be provided which merges functionality or code into existing classes or components. This
has some drawbacks: the code generator must replace every single method call by a dynamic one,
which is error-prone unless the code-generator includes a perfect source-code parser, or the codegenerator is perfect, and thus expensive and slow; the code generator must be invoked for your
original source code after each change. Another way is to integrate the dispatch mechanism into a
programming language, respectively a compiler. An approach for dynamic component
modification and integration into a JAVA-based language is shown by Kniesel G. in Type-Safe
Delegation for Run-Time Component Adaption [KNIES99]. The paper resumes in a very detailed
way the advantages and issues of dynamic component modification and delegation through
object-based inheritance.
With respect to the classification in Section 2.2.5, the flexible method dispatch presents an
approach to unanticipated, dynamic, selective, wrapper-based, object-preserving component
modification, quite like the Darwin model. With this approach we need not anticipate possible
future requirements for enhancements of our components, since modifications are generally
possible at runtime. Therefore this approach is dynamic and unanticipated. It is also selective,
because with bound Activities an Activity Set is associated with particular objects rather than
with a class. On the other hand, Activity Sets can also be used with unbound Activities only, and
therefore the concept is also suitable as class-based (global) approach. The approach is wrapperbased (Activity Sets can be wrappers for objects or components). The adapted wrapper can
coexist with the original component, or replace it. There can also be many wrappers of the same
object or component at the same time.
Let use resume some other aspects of the flexible method dispatch introduced in this thesis.
The advantages of the flexible method dispatch are:
v Dynamic modifications
w Method objects (Activities) can be added and removed at runtime, while almost no
reorganization of Dispatcher dictionaries is necessary for subclasses after adding or
removing method objects to/from a parent object.
In contrast, v-tables (like in C++ or Java) are static and the entries have static indices;
a method has the same index in the v-table of derived classes as in that of the base
class; reorganizing the related v-tables of derived classes after changing entries in the
base class would be very time-consuming at runtime; parts of v-tables of derived
classes have to be moved accordingly, if a base class gets a new method. See Figure
2.2-2.
Smalltalk method dictionaries need no reorganization at all after such a change, since
the base class methods are only referenced in the base-class method dictionary.
However, for looking up a method, the class hierarchy must be searched for the
method, which is time-consuming at runtime. A method lookup cache optimizes the
message dispatch drastically (according to [KRASN84], an appropriate method cache
can have hit ratios as high as 95%, reduce method lookup time by factor 9, and
increase overall method system speed by 37%).
With the flexible method dispatch, Dispatcher dictionaries only keep indirect
references to method objects (Activities). When an Activity is added to an Activity
Set, all delegation children get Dispatchers to the corresponding Dispatchers of their
parents.
See Program 2.5-1 and Program 2.7-4 for Dispatcher dictionary and references.
w With bound Activities (see Program 2.10-3), functionality can be added to an Activity
Set, although it is defined in another: bound Activities store a reference to its associated
2.13 Remarks
41
Activity Set, respectively its provider. This way, an Activity can always access the
environment (Activity Set) where they are deployed, and the environment, where they are
created (the Activity Set of the original provider).
w Activities, respectively their Dispatchers can be wrapped dynamically, aspects can be
added to Dispatchers: code can be added before and/or after the original method code by
wrapping it. See Program 2.8-1.
v Efficient dynamic method look-up
w A method object (Activity) respectively Dispatcher only needs to be looked up in the
Dispatcher dictionary of an object's associated Activity Set, and not also in its parents
like in the Smalltalk method look-up algorithm. We follow Dispatchers, starting with the
looked-up one until we get an Activity. See Program 2.4-1.
w Search of Activities in parents is done through directly linked Dispatchers. See Program
2.4-1 and Program 2.6-1.
w This solution is not as fast but more dynamic than v-tables where methods are looked up
with their integer index created at compile time.
w It is more efficient than message interpreters of Oberon where message is checked for its
type with WITH-DO clauses. The message type checking has runtime complexity O(n),
where n is the number of types to be checked. In contrast to that, the lookup-time of the
flexible message dispatch is constant.
v Dynamic reorganization of acquisition (dynamic inheritance) relationships. An Activity Set
can acquire or discard another at any time. See Program 2.11-1.
v Activity Sets can be dynamically grouped by setting their path adequately, or by setting the
parent path.
v Ordered multicasts (broadcasts) and other special dispatch strategies can easily be realized
(like in the message objects of Oberon), respectively are even included in the dispatch
mechanism. (Bound) method objects can be added to an Activity Set even if they belong to
others. All added Activities can be reached through Dispatchers. A Dispatcher may collect
all its descendants and call perform for each.
v Activities can be packaged with arguments and invoked later on. You find this feature also
in the Oberon message interpreter mechanism.
v Delegation through passed reference to the receiver. The scope (the Activity Set) where an
Activity originally was requested can be passed through the involved Dispatcher chain and
can be used for subsequent requests; different objects can use the same Activity Set; the
concept provides a common “this” for instances of independent classes.
v Encapsulation of the distribution of components. Activity Set contents can live behind
interface standards such as COM, XMLRPC, etc., but these interfaces can be hidden to the
developer for convenience; components and operations from various sources can be
attached and associated with Activity Sets dynamically.
v Hooks for method calls. Listeners can be attached to Dispatchers. They are notified before
and after an Activity is requested, respectively a corresponding Dispatcher is invoked. See
Program 2.9-1 and Program 2.9-4.
42
2 Method Dispatch and Delegation
v Method state support. Since methods are represented by objects, more precisely Activities,
they can be enhanced by features such as states—typical states are “Enabled”, “Disabled”,
etc. For example, beside method perform the Activity class could provide a method
getState to retrieve the current state, and a method stateChanged to notify listeners
of changes. States can be set and switched at any time. They may depend on factors such as
given application licenses (full version, demo version), etc. Activities may have different
behavior depending on the state. Common object oriented programming languages do not
support such a feature.
Disadvantages are:
v Increased memory footprint for Dispatcher dictionaries
w Compared to Smalltalk method dictionaries, additional entries for Dispatchers of parents
are necessary in Dispatcher dictionaries of children.
w Compared to v-tables, Dispatcher dictionaries are less memory-efficient. Dispatchers are
stored in dictionaries with strings as keys, instead of flat, memory-saving method pointer
arrays. V-tables also contain method pointers of base classes like the optimized
Dispatcher dictionaries, but a flat array is always smaller.
w There needs to be an Activity Set for each object or component where the flexible
method dispatch is applied. To avoid massive memory consumption it is reasonable to
use it rather for components than for each small object.
v Slower than v-tables. Dispatchers have to be looked up in dictionaries using their name,
respectively the hash-code of the name, as key. It depends mainly on how much time the
look-up of a Dispatcher name takes. The time for resolving Dispatchers is not relevant
(following the Dispatcher chain to a concrete Activity). In v-tables, the index representing a
method is calculated at compile-time; method look-up is only the access to the method
pointer at an index in the corresponding v-table.
v The static interface of a class does not reflect which messages can be handled by instances
of the class. It is difficult to realize at compile time which objects are related through
message forwarding at runtime.
v Invalid dynamic method calls are not recognized at compile time, as opposed to static
method calls.
In contrast to the proposed solution (indirect references), a less time-consuming solution is to
store references to the Activities of parents directly in the Dispatcher dictionaries of the children,
similar to v-table-approach.
However, in the case of a v-table-approach, method indexes have to be recalculated. Parts of vtables of derived classes must be moved when a method is added or removed in a base class. For
example, if the method clone is added to the base class Object, a pointer to this method has
to be added to the v-table of MyPoint as well as to those of all other derived classes. The tricky
thing is that the new method must be added to the v-table of MyPoint at the end of the section
that comes from Object and before the pointers to the methods implemented directly in
MyPoint. It gets even more complicated, if multiple inheritance is supported (which we use for
multicasts). In this case, it must be ensured that a method that is inherited from more than one
base class gets the same method index in the v-tables of all related classes. See Figure 2.2-2 for
an overview of v-tables.
Also, like v-tables, Dispatcher dictionaries of all child Activity Sets have to be reorganized,
when an Activity is added to a parent. The difference is, that the positions of Dispatchers in
Dispatcher dictionaries are not relevant, since they are looked up via names and not via precalculated indexes.
2.13 Remarks
43
When using direct references or pointers to methods (such as v-table method pointers), the
method look-up may be slightly faster (for inheritance depth > 1) than with indirect references,
but the impact is only n × <time for a method call> (see Program 2.4-1), where n is
the inheritance depth of the current class.
With our approach, client code can attach listeners to Dispatchers at each indirection level, which
allows clients to be notified whenever an Activity on a specific class is invoked. This is useful
for tracing calls or adding aspects from different sources.
Finally, the flexible method dispatch enables delegation across independent objects, and dynamic
component modification, using Activity Sets, Dispatchers, and bound Activities. Each Activity
of an Activity Set may be bound to a different object. In fact, in PCoC, each Activity is in any
case bound to an object. There are no unbound Activities. That is, they are always used on object
level (selective approach), rather than on class level (global approach).
Like .NET delegates, Dispatchers can be used for multicasts, and for forwarding to methods (in
our case through Activities) of different objects or classes.
Nevertheless, there are still some open issues. The performance and memory-consumption can be
improved through better implementations. The current implementation as framework is used in a
productive environment (for a commercial application), and will be reworked in order to get
cleaner code. The dispatch mechanism could be integrated in a programming language in order to
make it easier to use.
The following sections give a detailed insight in how these concepts are deployed.
44
2 Method Dispatch and Delegation
3 PCoC Terms
45
3 PCoC Terms
This section explains essential basics of PCoC for better understanding its usage as shown in
Chapter 4, Using PCoC.
3.1 Overview
An application can basically consist of interactive and non-interactive parts—so-called
components.
In the context of this thesis, we use the term Tool for interactive components of (G)UI
applications. A Tool provides a user interface (expects user interaction) and operations which we
call Activities. For example, a word processor component can be a Tool.
Tools need Materials to work on. For this reason we use the term Material for containers of
Activity arguments and return values.
The meanings of the terms Tool and Material are quite similar to those of the WAM-metaphor.
WAM stands for Werkzeug-Automat-Material (Tool-Automaton-Material). See [ZULLIG98].
Note that the term Tool is used for many different things in other sources. People use it for whole
applications (grep tool, shell tool, image manipulation tool), for objects (a selected brush, pen, or
crop utility in an image manipulation program), and also for very basic things such as the mouse
cursor.
The non-interactive components of PCoC applications are called Service Providers. For example,
a component providing file system functionality (moveFile, openFile, etc.) via
Activities is a Service Provider. Both, Tools and Service Providers are so-called Activities
Providers—components which expose a part of their functionality as Activities.
The following sections describe PCoC terms and their meanings, and differences to definitions of
the WAM-metaphor.
3.2 Activities Providers
3.2.1 Overview
An Activities Provider is a component providing its functionality via Activities. The figure below
illustrates how PCoC classes are related to each other; the annotations explain the responsibilities
of the framework classes.
The classes highlighted in grey are those implemented by the component developer: Service
Providers, the services they provide, and Simple Tools. A Combined Tool is a generic class, and
is created and initialized by PCoC depending on the configuration. See Section 4 for details.
The Activities, Dispatchers, and dynamic containment and acquisition information of an
Activities Provider are kept in its Activity Set which is created when the Activities Provider is
initialized.
46
3 PCoC Terms
Activity Set
acquiredSets
{ordered} <<acquire>>
container
1
*
*
*
Activity Set
1
the name service
used for loosely coupling
Activities Providers, providing
hierarchical structuring,
acquisition, and priority
management at runtime.
activitySet
ServiceProvider
<<component configuration>>
(resources, properties)
Tool
<<instantiation>>
provider
1 configuration
1
element
*
a unit that
can be deployed
independently of its
environment. Contains
and manages necessary
properties and resources.
1
Activities Provider
1
container
*
<<use>>
*
ServiceProvider
<<service>>
CombinedTool
1
*
SimpleTool
Activities Providers
providing specific services
via Activities and having a
presentation--a graphical
or character based
user interface.
<<instantiation>>
1
1
*
an Activities Provider
providing specific
services via Activities.
<<datamodel>>
basic services,
for example
file system
operations
Generically
created from
definition in
the component
configuration
*
<<datamodel>>
0..1
0..1
<<presentation>>
(view)
Two ways to implement:
- an own class
or
- a composition created
by the tool
Figure 3.2-1 Activities Provider Architecture
See the sections below for definitions of Tool, Service Provider, Activity, Dispatcher, and Task.
3.2.2 Class(es)
The corresponding class is PCCActivitiesProvider.
3.3 Activity Sets
3.3.1 Overview
An Activity Set is an object providing a set of operations called Activities, their Dispatchers
(which delegate requests for invoking Activities to the corresponding Activity Sets), and a
corresponding lookup mechanism for Activities and Dispatchers. It is often associated with an
Activities Provider (see above). Activity Sets are distinguished by their path (for example,
“|A|B”).
This concept is useful for building acquisition relationships, and for grouping semantically
related objects or components. See also Section 2.4 and Section 2.12.
Activity Sets are added automatically to a registry. The registry is realized as a singleton class
PCCRegistry. It can be used to look up Activity Sets (using their path), and their associated
Activities Providers.
3.3.2 Class(es)
The corresponding class is PCCActivitySet.
3.4 Tools
47
3.4 Tools
3.4.1 Overview
A Tool is an Activities Provider which expects user interaction. Tools provide a specific object
as visual representation, for example a JComponent in Java. The main functionality (operations
on data, algorithms, etc.) is directly implemented in the Tools class.
In the WAM-metaphor, the term Tool can, for example, stand for a notepad or organizer
application. In the context of this thesis, Tools are rather those parts of applications that actually
provide the functionality. As in the WAM-metaphor, [ZULLIG98], Page 280, Simple Tools can
be combined to more complex Tools, which are then called Combined Tools.
Tool component
provides
and performs
Activities
<<Tool class>>
1
1
1
1
<<Activity Set>>
<<GUI representation>>
an
existing, PCoCindependent component,
for example a
JComponent in Java
Swing.
naming service and
context, holding an
ActivitiesProvider's
operations (Activities)
Used for
coupling and control
of components
Figure 3.4-1 A Tool
Figure 3.4-1 depicts the relationships between some classes that form a Tool.
3.4.2 Examples
Figure 3.4-2 shows what Tools look like in the context of PCoC and this thesis.
Application
Tools
Figure 3.4-2 A PCoC application
Tools can be composed to more complex Tools. The following figure shows a typical Combined
Tool (ProjectManager) containing some Simple Tools.
48
3 PCoC Terms
ProjectManager: CombinedTool
:ProjectBrowserTool
:FileSelectionTool
Figure 3.4-3 A Combined Tool
ProjectBrowserTool and FileBrowserTool are Simple Tools—they are classes
(directly or indirectly) derived from PCCSimpleTool. Combined Tool is a generic class not
subject for deriving subclasses. Instances, such as ProjectManager, are automatically
generated from configuration files.
Simple Tools can also contain other Simple Tools, but they are not generated automatically in
contrast to Combined Tools. Composition must be done in source code. This may be solved
differently in other frameworks.
3.4.3 Class(es)
The corresponding classes are PCCSimpleTool and PCCCombinedTool.
3.4.4 Remarks
Chapter 4, Using PCoC, explains Tools and their collaboration in more detail.
3.5 Service Providers
3.5.1 Overview
Services, such as file system functionality, can be realized as Service Providers. A Service
Provider is a non-interactive Activities Provider, as opposed to Tools. It exposes services via
Activities.
It is basically implemented like a Tool, except that it does not provide a specific GUI
representation object. Figure 3.5-1 depicts the relationship between some classes that form a
Service Provider.
3.5 Service Providers
49
Service Provider component
<<Service Provider class>>
provides
and performs
Activities
1
1
<<ActivitySet>>
naming service and
context, holding an
ActivitiesProvider's
operations (Activities)
Used for
coupling and control
of components
Figure 3.5-1 A Service Provider
3.5.2 Class(es)
The corresponding class is PCCServiceProvider.
3.5.3 Remarks
Chapter 4, Using PCoC, describes a sample implementation and configuration of a Service
Provider.
3.6 Activities
3.6.1 Overview
Activities are operations provided by Activities Providers (Tools, Service Providers). An Activity
is implemented as class and is treated as first class object by PCoC.
Activities correspond to bound method objects as described in Section 2.10 (Program 2.10-3).
An Activity can be instantiated by an Activities Provider, and can subsequently be performed. A
single method (doPerform) of an Activity implements its actual functionality. See Program
4.2-3.
Activities can be retrieved, and added to and removed from Activity Sets at runtime. This is an
enhancement to Java reflection, which supports retrieving of methods at runtime, but not adding
them to objects at runtime.
Activities support different states, including “Enabled” and “Disabled”.
Detailed concepts and examples of Activities are described in the following sections.
50
3 PCoC Terms
AbstractActivity
0..1
macro <<script>>
1
*
*
task
configuration
Task
*
<<instantiation>>
1
delegates
Activity invocation
requests,
using directives
*
prioritized
delegation
(distribution)
1..*
*
Dispatcher *
*
<<configuration>>
(resources, properties)
Activity
activitySet
1
1
acquiredSets
{ordered} <<acquire>>
container
1
*
*
*
Activity Set
1
*
persistent storage
(for toolbars, menus,
etc.) or temporary
(for user code)
<<instantiation>>
Create or update
Dispatcher when
an Activity is
added to the
Activity Set
1
0..1
for each
local or acquired
Activity
<<instantiation>>
1 activitySet
element
*
1 provider
1
1
provider
Activities Provider
container
Figure 3.6-1 Activity relationships
This picture illustrates how Activity Sets, Activities, Dispatchers, and Tasks are related to each
other. See later in this chapter for definitions of the terms Dispatcher and Task.
3.6.2 Class(es)
Base class for all kinds of Activities is PCCAbstractActivity. Concrete classes must be
derived from PCCSingleActivity or PCCMultiActivity which are both derived from
PCCActivity. A PCCSingleActivity represents a single operation, e.g. moveFile to
move a file in a file system. PCCMultiActivity represents a group of Activities with the
same interface, e.g. showRecentFile to reopen a recently opened file in a proper Tool. The
Activities of this group are accessed with an index.
Note that there are other classes derived from PCCAbstractActivity which semantically
differ from single and multiple Activities. Examples are PCCDispatcher and PCCTask (see
Figure 3.6-1).
3.6.3 Remarks
Do not confuse PCoC Activities with COM+ Activities. In the context of COM+, respectively
Microsoft Transaction Server (MTS), Activities are logical threads running across different
machines. They are used for cooperation of COM+, respectively MTS objects. See also
[STAL01], Page 291.
3.7 Materials
3.7.1 Overview
In [ZULLIG98], Page 86, Material is defined as an object, which can be an element in a
container, and on which Tools can operate in a working environment. For example, documents
and files are considered as Materials. We agree with this definition, although the term is more
limited in this thesis (see below).
According to [ZULLIG98], on Page 89, an object holding Materials is defined as a container,
therefore an argument list would be a container.
3.7 Materials
51
In the context of this thesis, a Material is a list of arguments passed to Activities or a list of return
values provided by them. A Material can also contain other Materials, but this is an exception to
the rule. Usually, a Material is just a container and something that a Tool can operate on. An
Activity (for example cut) of a Tool can only be performed with a specific Material type (the
parameter types must match the Activity interface).
3.7.2 Class(es)
The corresponding class is PCCMaterial.
3.8 Dispatchers
3.8.1 Overview
One of the most relevant concepts of PCoC are Dispatchers. A Dispatcher forwards or delegates
requests for the invocation of a specific type of Activity to Activities Providers. Dispatchers are
used for component collaboration and within Tasks. A set of directives specifies how to forward
requests, e.g., as single or multicasts.
Dispatchers correspond to indirect method references as described in Section 2.4. Figure 2.4-2
illustrates relationships between Dispatchers, Activities, and Activity Sets.
Dispatchers are similar to the Microsoft .NET delegates (type-safe method-pointers). See Section
6.7.5. The main difference to delegates is that Dispatchers can be used for delegation, whereas
delegates can only be used for forwarding. With forwarding, the object where a method
originally has been invoked (the receiver) is not known to the method holder (where the method
is actually executed), and therefore the method holder cannot use it for subsequent method calls.
Dispatchers are automatically generated when an Activity is added to a component, respectively
to its associated Activity Set, or acquired from another. Their type safety is ensured at runtime,
whereas the type safety of .NET delegates is ensured at compile time.
The set of Dispatchers available in an Activity Set builds a kind of method dictionary. A
Dispatcher has a reference to an Activity (if one with the same name exists in the same Activity
Set), and a list of references to “acquired” Dispatchers (see Program 2.6-1). When a Dispatcher is
invoked (for example, by using its perform method), it gathers all Activities by following the
directly or indirectly connected Dispatchers and invokes them, depending on specified directives.
Directives determine how Activities are invoked. This includes single casts or multicasts. We say
that these Activities are in the dispatch or acquisition branch of the Dispatcher.
See Program 2.6-1 and Program 2.11-3.
3.8.2 Class(es)
The corresponding class is PCCDispatcher. An instance of this class serves as proxy for an
PCCDispatcherImpl object, i.e. it forwards method calls to its associated DispatcherImpl
object, which does the actual forwarding to Activities. A reference to a Dispatcher can be kept,
even if the corresponding DispatcherImpl is not available (any more). As soon as the
DispatcherImpl becomes available (after loading or activating the corresponding Activities
Provider and its Activities), its Dispatcher becomes functional. See 5.8.3.
3.8.3 Remarks
See Sections 5.4 and 5.6 for more details about Dispatchers.
52
3 PCoC Terms
3.9 Tasks
3.9.1 Overview
A Task is a meaningful unit of work, composed of a series of steps that lead to some well-defined
goal. In the context of this thesis, it is a complex Activity composed of a series of Dispatchers
including directives and arguments, and/or other Tasks. Specified conditions must be met to
enable it for invocation.
A simple Task is the opening of a text editor component with a subsequent loading of a text file
and the positioning of the caret at the beginning of the corresponding text view.
The term Task corresponds to the term automaton of the WAM-metaphor. [ZULLIG98] defines
the term automaton on Page 89 as a machine that is used to perform a specific task. It can
autonomously operate on given Materials. It has preconditions that must be met, and if they are,
then the automaton performs a series of steps to finish the task. Note that the term automaton is
mostly used with another meaning—for state machines (for example, deterministic and nondeterministic finite automaton).
The main view of this thesis is not the automatism, which allows performing a Task, but the
Task itself. The ability of autonomously executing a Task is a feature of a Tool or Service
Provider in the context of this thesis, rather than a separate operative object (automaton).
3.9.2 Class(es)
The corresponding class is PCCTask.
3.9.3 Remarks
See also Section 4.5 for usage of Tasks, and Section 5.7 for more detailed concepts of this design
element.
3.10 Case Insensitivity in PCoC
Activity, Dispatcher, and Activity Set names are always handled case insensitively. This is for
the convenience of framework users. No one intuitively distinguishes, for example, between
copy, Copy, and cOpy, even if most programming languages force developers to do that.
Activity Sets and Activities can be looked up case-insensitively. For example, an Activity created
with name “cOpy” can be looked up as “copy”. All Activities with the same case-insensitive
name in the system must have the same interface (argument and result types) at runtime. If an
Activity with name “Copy” and runtime interface void Copy(Selection) is added, and
another Activity “cOpy” with interface void cOpy(), an error will be thrown.
3.11 Class Hierarchy
53
3.11 Class Hierarchy
Figure 3.11-1 shows the hierarchy of PCoC classes.
Object
PCCAbstractActivity
PCCActivity
PCCSingleActivity
PCCMultiActivity
PCCDispatcher
PCCDispatcherImpl
PCCTask
PCCActivityInterface
PCCMaterial
PCCDirectives
PCCActivitiesProvider
PCCSimpleTool
PCCCombinedTool
PCCServiceProvider
PCCActivitySet
Figure 3.11-1 PCoC class hierarchy
These classes are named as described in Chapter 3, where detailed explanations to these elements
are provided.
Note that all PCoC classes have the prefix “PCC”. In most of the figures in this thesis the
prefixes have been omitted for simplicity.
54
3 PCoC Terms
4 Using PCoC
55
4 Using PCoC
This chapter describes the implementation and customization of components based on PCoC.
4.1 Overview
PCoC components can be easily created in a few steps:
v Derive a class from a small base class: For interactive components (components with
graphical user interface) derive from PCCSimpleTool, and for non-interactive
components from PCCServiceProvider. See Section 4.2.
v Set up a set of Activities. See Section 4.3.
v Use Dispatchers or Tasks to invoke Activities. See Sections 4.4 and 4.5.
4.2 The First Tool
Program 4.2-1 and Program 4.2-3 show a first Simple Tool. The class SimpleTool1 derives
from PCCSimpleTool, overriding makePresentation to return a visual representation of
itself. All presentation objects of PCoC Tools currently require Java Swing (respectively ET++ in
C++). In this case a JScrollPane containing a list of four strings is displayed.
makePresentation is subject to be overridden by each Simple Tool.
Program 4.2-1 SimpleTool1.java (1)
package example1;
import pcoc.tools.*;
import javax.swing.*;
/** A Tool providing and displaying a list of strings.
*/
public class SimpleTool1 extends PCCSimpleTool {
JList fList;
/** Constructor. Base classes do some initialization.
*/
public SimpleTool1() {}
/** Set up the Tool's GUI
* @return A JComponent representing the GUI of this Tool
*/
public JComponent makePresentation() {
String[] data = "Hello World!";
fList = new JList(data);
return new JScrollPane(fList);
}
/** Set up the Tool's Activity Set which holds and manages an
* Activities-Provider's Activities.
*/
protected void setupActivitySet() {
super.setupActivitySet();
addActivity(new ClipboardPasteActivity()); // add an Activity and set this
}
// as its provider (fProvider)
...
setupActivitySet instantiates and adds Activities (operations) to this Activities Provider.
In this case we add an instance of ClipboardPasteActivity. See Program 4.2-3 for the
implementation of this class.
56
4 Using PCoC
Program 4.2-2 SimpleTool1.java (2)
...
/** Do some extra initialization. doActivate is called after basic
* initialization of the Activities Provider. */
public void doActivate() {
... // do something, for example synchronize data models between siblings
}
/** Do some extra deinitialization. doTerminate is called before basic
* deinitialization of the Activities Provider. */
public void doTerminate() { ... // do something }
...
doActivate and doTerminate can be overridden to perform required tasks in the startup
and termination phase of a component. Examples for required tasks are data synchronization,
sharing of data, adding references to other objects, or removing references.
Program 4.2-3 SimpleTool1.java (2)
/** A concrete Activity which is an implementation of a specific operation-in
* this case paste from clipboard.
class ClipboardPasteActivity extends PCCSingleActivity {
/** Activity constructor. It sets the "dynamic" type of this operation.
* Type declaration: name "clipboardPaste", category "Edit",
*
result type (void: ""), parameter types (void: "")
ClipboardPasteActivity() { super("clipboardPaste", "Edit", "", ""); }
/** Perform an Activity. It throws an exception if an unexpected error
* occurs during its execution.
* @param si Sender and receiver context (involved Activity Sets)
* @param arg The argument list provided for the execution
* @return The result(s) of the performed Activity
*/
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial arg)
throws PCCPerformException {
String str = Clipboard.getAsString(); // add clipboard content
if (str != null && !str.equals("")) { // to list
((SimpleTool1)fProvider).addElement(str);
}
return null;
}
/** Get the current state; is triggered by a direct call of update or
* indirectly by a call of updateActivity of the provider of the Activity.
* @return The current state of this Activity
*/
protected String doGetState() {
return defaultState(true); // is always "Enabled" in this case
}
}
... // other Activity classes
} // end of SimpleTool1
Program 4.2-3 shows a simple implementation of an Activity. In the constructor, we specify its
“dynamic” type (its name, category, and interface). The dynamic type of Activities is described in
Section 4.3.4.
doPerform implements the core functionality of this Activity defined in the context of the
component in Program 4.2-1. It is the most relevant method and must be overridden. It is called
indirectly when we call the perform method of this Activity.
The PCCSenderInfo object holds references to the Activity Set where the Activity has been
invoked (the receiver), and to the caller (the sender). The receiver is not necessarily the Activity
Set where the Activity finally is executed. The structure usually is filled by PCoC.
4.2 The First Tool
57
doGetState returns the current state of this Activity. This method must also be overridden. It
can return any state represented as a string. For the most common states, there are predefined
methods:
defaultState(bool),
disabledState()
(corresponds
to
defaultState(false)), enabledState() (corresponds to defaultState(true)).
In the disabled state an Activity is not executable, i.e., doPerform will not be executed when
the Activity is invoked. In the enabled or another state, the Activity is executable, i.e., a call of
perform will lead to an invocation of the doPerform method.
Note that in the given example, an Activity is explicitly defined. There are easier ways to create
Activities. See the following code fragment.
Program 4.2-4 Semi-automatic generation of Activities
public class SimpleTool1 extends PCCSimpleTool {
protected void setupActivitySet() {
super.setupActivitySet();
addActivity("doPaste", "getPasteState");
}
void doPaste(String, String) {...}
String getPasteState() {...}
}
With this code fragment, an Activity is implicitly created by the framework, and added to the
Activity Set of SimpleTool1. The framework uses the Java reflection mechanism to retrieve
the methods doPaste and getPasteState via their names and associate them with the
methods doPerform and doGetState of the newly created Activity. When the Activity
doPaste is invoked (by calling perform), the call is delegated to the corresponding method
doPaste using reflection. When the state of the Activity is retrieved, the corresponding method
getPasteState is invoked. This approach is explained in detail in Section 5.9.3.
A Tool’s visual representation is automatically embedded in a window by PCoC’s frame
manager. How this is done depends on the application’s configuration. Program 4.2-5 shows,
how SimpleTool1 may be embedded into a Combined Tool (CombinedTool1). After the
Tool is launched, it is shown as child window of the application main window.
Program 4.2-5 Component configuration
<?xml version = "1.0" encoding = "UTF-8"?>
<ResConfig name="Example1" xsi:schemaLocation="www.windriver.com/ResConfig
file:///pwe/rome/config/tools/ResConfig.xsd" xmlns="www.windriver.com/ResConfig"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance">
<RCCombinedTool>
<name>FrameManager</name>
<menuBarName>FrameManagerMenuBar</menuBarName>
</RCCombinedTool>
<RCSimpleTool>
<name>SimpleTool1</name>
<className>example1.SimpleTool1</className>
</RCSimpleTool>
<RCCombinedTool>
<name>CombinedTool1</name>
<windowTitle><dockedTitle>My Combined Tool</dockedTitle></windowTitle>
<part><simpleToolName>SimpleTool1</simpleToolName></part>
</RCCombinedTool>
...
This configuration contains all necessary definitions to set up an application to be able to launch
our first Tool. It contains definitions of our Simple Tool, and a Combined Tool containing it.
Definitions for a menu bar (FrameManagerMenuBar), a menu (Example1ToolsMenu),
and a menu entry (showCombinedTool1) follow below.
58
4 Using PCoC
Program 4.2-6 Component configuration: menu items and tasks
...
<RCMenuBar>
<name>FrameManagerMenuBar</name>
<menuNames>
<menuName>Example1ToolsMenu</menuName>
</menuNames>
</RCMenuBar>
<RCMenu>
<name>Example1ToolsMenu</name>
<mnemonic>T</mnemonic>
<shortDescription>Tools</shortDescription>
<items>
<item name="showCombinedTool1" type="task"/>
</items>
</RCMenu>
<RCTask>
<name>showCombinedTool1</name>
<mnemonic>1</mnemonic>
<shortCut>control 1</shortCut>
<dispatcher>|Application|launchCombinedTool1</dispatcher>
<RCStates>
<RCState name="Enabled">
<shortDescription>Show CombinedTool1</shortDescription>
</RCState>
<RCState name="Disabled">
<shortDescription>Show CombinedTool1</shortDescription>
</RCState>
</RCStates>
</RCTask>
</ResConfig>
Now, we can use the menu item showCombinedTool1 to launch our Tool.
showCombinedTool1 causes the Dispatcher launchCombinedTool1 to be invoked
which delegates calls to the associated Activity. The framework generates such an Activity for
each Combined Tool. The name of the Activity is that of the Combined Tool prefixed by
“launch”.
An RCTask defines the look and feel and behavior of a Task. The structure defines the
mnemonic key Alt/1, and the keyboard shortcut Ctrl/1 for invoking the task. The
RCStates section describes the look of the menu or toolbar item for the current state of the
associated Task.
A Task consist of a Dispatcher and the arguments necessary for the execution of the Task. Each
argument can itself be a Dispatcher or Task returning an object as argument for the main
Dispatcher in the Task. A Task has also a list of references to other Tasks to be used as a macro
(script).
We can simply add a menu item for our Activity ClipboardPasteActivity to menu
Example1ToolsMenu by inserting the following line into its items section.
Program 4.2-7 Component configuration: using RCTask definitions
<item name="clipboardPaste" type="task"/>
In addition, we have to define a RCTask and associate it with the menu item. See Program 4.28. The corresponding Task object (Activity) will be generated by the framework when the
configuration is loaded. When a menu or toolbar item associated with this Task is selected, the
method perform of the associated Dispatcher clipboardPaste in Activity Set
Application is invoked. The Dispatcher delegates the request to the corresponding Activity
of the Activity Set that has the focus. That is, the method doPerform of this Activity is finally
executed.
4.2 The First Tool
59
Program 4.2-8 Component configuration: RCTask definition
<RCTask>
<name>clipboardPaste</name>
<shortCut>control v</shortCut>
<dispatcher>|Application|clipboardPaste</dispatcher>
<RCStates>
<RCState name="Enabled">
<shortDescription>Paste</shortDescription>
</RCState>
<RCState name="Disabled">
<shortDescription>Paste</shortDescription>
</RCState>
</RCStates>
</RCTask>
See Sections 4.5 and 5.7 for details about Tasks.
4.3 Using Activities
In the sections before we saw how an Activities Provider looks like and how its body can be
implemented. Now we concentrate on the implementation of Activities, sending requests for
performing Activities, and dynamic coupling and control of Activities Providers.
4.3.1 Activity Class Interface
This section describes the most relevant methods of Activities.
Program 4.3-1 Class interface of PCCAbstractActivity (1)
public PCCAbstractActivity(String name, String category,
String resultType, String argumentType) {...}
/** Get activity name specified in the constructor.
*/
public String getName() {...}
/** Get activity category specified in the constructor.
*/
public String getCategory() {...}
/** Perform an Activity
* It throws an exception if an unexpected error occurs during its execution.
* Calls the method doPerform.
* @param arg The argument list provided for the execution
* @return the result(s) of the performed Activity
*/
public PCCMaterial perform(PCCMaterial arg) throws PCCPerformException {...}
/** Returns the current state; is triggered by a direct call of update
* or indirectly by a call of updateActivity of the provider of the Activity.
* Calls the method doGetState, if the state was previously
* invalidated with update.
* @return The current state of this Activity
*/
public String getState(PCCMaterial arg) {...}
/** Invalidate the state of this Activity. A subsequent call of getState leads
* to a call of doGetState. If update has not been called, the cached state is
* returned by getState.
*/
public void update() {...}
Program 4.3-1 shows the most relevant methods of a PCCAbstractActivity. The class
provides default implementations for Activities, Dispatchers, and Tasks. The class
PCCActivity is the base class for Activities which are subject to be derived in client code.
60
4 Using PCoC
Actually, we derive from its subclasses—the classes PCCSingleActivity and
PCCMultiActivity. When deriving from these classes, we have to override at least the
methods doPerform and doGetState (which are called from the template methods
perform and getState).
A PCCSingleActivity represents a single operation, e.g. moveFile to move a file in a file
system. PCCMultiActivity represents a group of Activities with the same interface, e.g.
showRecentFile to reopen a recently opened file in a proper Tool. The Activities of this
group are accessed with an index parameter (the first argument in arg).
Although Activities should rather be used together with Dispatchers (see Section 4.4), they may
also be used directly. However, in this case it makes more sense to use ordinary methods instead
of Activities.
The following methods are less frequently used, but nevertheless important.
Program 4.3-2 Interface of PCCAbstractActivity (2)
/** Get the activity path. It consists of the path of the Activity Set to which
* this Activity has been added, and the Activity name.
*/
public String getPath() {...}
/** Set context specific data, for example, additional information to the current
* selection in the GUI. Context data is used, for example, for context sensitive
* menu entries. This method must be used in doGetState of this Activity
* @param key The key for a new entry in the context data table of this Activity
* @param value The value to be associated with the given key
*/
public void setContextData(String key, String value) {...}
/** Get context-specific data, for example, additional information on the current
* selection in a text editor.
* @param key The key for a new entry in the context data table of this Activity
* @param value The value to be associated with the given key
*/
public String getContextData(PCCMaterial arg, String scope, String key) {...}
/** add listener to this Activity
* @param the listener to add
*/
public void addListener(PCCActivityListener listener) {
/** remove listener from this Activity
* @param the listener to remove
*/
public void addListener(PCCActivityListener listener) {
See Section 4.3.7 for a detailed description of Activity states and context data. See Section 4.3.2
for a description of the listener interface.
Context data denotes a set of strings, representing data in addition to the return value and state of
an Activity.
4.3.2 Activity Listener Interface
The following interface is used to send notifications for state changes of Activities, and when
Activities are added to or removed from a Dispatcher, Task, etc.
4.3 Using Activities
61
Program 4.3-3 ActivityListener interface (1)
/** ActivityListener interface
* Used for Activities, Dispatchers, and Tasks
*/
public interface PCCActivityListener {
/** Activity has changed its state. This method is called in a.update().
* Retrieve the current state by using a.getState()
* @param a the Activity whose state has changed
*/
public void activityChanged(PCCAbstractActivity a);
/** Activity is added to a. a must be Dispatcher, a Task, or a
* MappedActivity (an Activity associated with a Task)
* @param a the Activity to which another Activity has been added
*/
public void activityAdded(PCCAbstractActivity a);
/** Activity is removed from a. a must be Dispatcher, a Task, or a
* MappedActivity (an Activity associated with a Task)
* @param a the Activity from which another Activity has been removed
*/
public void activityRemoved(PCCAbstractActivity a);
...
The following methods are called in the perform method of the given Activity a.
Program 4.3-4 ActivityListener interface (2)
...
/** Activity is going to be performed. This method is called before a.doPerform
* @param a the Activity to be performed
*/
public void beforePerform(PCCAbstractActivity a, PCCMaterial args);
/** Activity has been performed. This method is called after a.doPerform
* @param a the Activity which has been performed
*/
public void afterPerform(PCCAbstractActivity a, PCCMaterial args,
PCCMaterial result);
}
4.3.3 PCCMaterial: Container for Arguments and Return Values
PCCMaterial is a typed container for arguments and return values of Activities. The types of
objects contained in a PCCMaterial must match the interface specified in the constructor of an
Activity.
See also Section 5.5.1 for details about Activity interface specification and checks (type-safety).
The PCCMaterial class as illustrated in Program 4.3-5 offers constructors for primitive data
types, for an object, and a default constructor. For single values, the constructors should be
sufficient. For more complex lists there are methods for adding values to, or setting values in, the
container. The interface can be retrieved as string by using the getInterface method.
We use an ArrayList to hold the objects added to a Material, which is enough for our
purpose. We may use HashMap instead, for accessing arguments by name, instead of by index.
However, this would make the specification of Activity interfaces and arguments via Materials
even more complicated, so we decided to use ArrayList.
62
4 Using PCoC
Program 4.3-5 PCCMaterial.java (excerpt)
public class PCCMaterial {
final public static int NPOS = -1;
ArrayList fArgs;
/* Default Constructor. Empty list. */
public PCCMaterial() {...}
/* Constructors for convenience. Basic data type values are automatically
* converted to appropriate objects */
public PCCMaterial(Object value) {...}
public PCCMaterial(String value, boolean splitString) {...}
public PCCMaterial(boolean value) {...}
public PCCMaterial(char value) {...}
public PCCMaterial(int value) {...}
public PCCMaterial(double value) {...}
/* Get a value */
public Object getObject(int index) {...}
public boolean booleanValue(int index) {...}
char charValue(int index) {...}
public int intValue(int index) {...}
public double doubleValue(int index) {...}
/* Add
public
public
public
public
public
a value */
void add(Object element) {...}
void add(boolean element) {...}
void add(char element) {...}
void add(int element) {...}
void add(double element) {...}
/* Set
public
public
public
public
public
a value */
Object set(int
Object set(int
Object set(int
Object set(int
Object set(int
index,
index,
index,
index,
index,
Object element) {...}
boolean element) {...}
char element) {...}
int element) {...}
double element) {...}
/* Remove values */
public void remove(int index) {...}
public void clear() {...}
/* Content checks */
public int size() {...}
public boolean isNull() {...}
public String getInterface() {...}
// get types of contained objects as
// interface string "String,Boolean"
public Class getType(int index) {...}
public boolean isSubclass(int index, Class cl) {...}
...
}
Note that primitive data types are converted to instances of the corresponding class. For example,
an int value 17 is converted to an Integer instance with value 17.
The concrete implementation of this class is not shown, since it is trivial.
4.3.4 Dynamic Activity Type Explained
Activities have a static type which is its class, and a dynamic type which consists of its name, its
interface, and category. For example, an Activity might have the name clipboardPaste, the
interface “void clipboardPaste(void)”, and might belong to category Edit (cf.
Program 4.2-3 and Program 4.3-6). The name specified in the constructor
(“clipboardPaste”) may be different to the class name (ClipboardPasteActivity).
The result type and the list of parameter types are specified as comma-separated lists of class
names. PCoC creates and shares instances of the class PCCActivityInterface for the
specified type lists. See Section 5.5.1 for more details about PCCActivityInterface.
4.3 Using Activities
63
Program 4.3-6 An Activity with no return value and parameters
/** Activity constructor. It sets the "dynamic" type of this operation.
* Type declaration: name "clipboardPaste", category "Edit",
*
result type (void: ""), parameter types (void: "")
ClipboardPasteActivity() { super("clipboardPaste", "Edit", "", ""); }
In this example, the Activity does not need parameters and does not return a result.
An Activity with a more complex interface is illustrated below:
Program 4.3-7 An Activity with a return value and parameters
/** Activity constructor. It sets the "dynamic" type of this operation.
* Type declaration: name "moveFile", category "File", returns a Boolean object,
*
needs two strings, the original file path and the new one
MoveFileActivity() { super("moveFile", "File", "Boolean", "String,String"); }
The Activity takes two String objects as parameters and returns a Boolean object as result in
the template method perform, respectively in the method doPerform which implements its
concrete behavior.
Activity Categories, such as “File” in the example above, can be used, for example, for
collectively invalidating states, or triggering the invocation of activities. Generally, categories
have been introduced only for convenience. A category name is part of the (dynamic) type of an
Activity.
An Activity name is bound to a category. For example, it is not allowed to add an Activity copy
with category Edit, and one with category Clipboard to the system. This would cause a
clash in an Activity Set that acquires two others, one containing a Dispatcher to one of these
Activities, and one containing a Dispatcher to the other Activity (see Section 5.4.3). The
Dispatcher in the acquiring Activity Set would have to delegate requests to Dispatchers with
different dynamic types—in this case different categories. Such a clash of Dispatcher types is
always reported by the framework.
Remember, Dispatchers always have the same dynamic type as the associated Activities,
respectively its acquired Dispatchers. We say, an Activity Set A or Dispatcher DA acquires
(“dynamically inherits”) a Dispatcher DB, when A acquires Activity Set B containing DB, where
DA and DB have the same name.
A clash would also exist if an Activity copy with interface void copy(selection) and
one with boolean copy(void) was added.
See Section 5.5.1 for a detailed explanation of the usage and concepts of result and parameter
types of Activity.
4.3.5 Creating and Setting Up Activities
In the constructor of an Activity we specify its dynamic type. This includes its name, category,
return value and parameter list interfaces. See Section 4.3.4 for a detailed description of the
dynamic Activity type.
See also Section 5.5.1 for a description of the Activity Interface class. Instances of this class are
used to represent the parameter and result type of an Activity.
Note that Activities should always be an inner class of an Activities Provider to have a clear
design.
64
4 Using PCoC
Program 4.3-8 PCCSingleActivity constructor
public class PCCSingleActivity {
/** Activity constructor. It sets the dynamic type of this Activity and
* must be called in the constructors of derived classes.
* @param name The name of the Activity. All Activities with the same
*
name must have the same dynamic type (category, resultType, argumentType)
* @param category A virtual category to which this Activity belongs. This
*
can be used to update the states of all Activities of a category at once,
*
or to remove them from their Activity Sets.
* @param resultType Result type. See PCCActivityInterface.
* @param argumentType Parameter types. See PCCActivityInterface.
public PCCSingleActivity(String name, String category,
String resultType, String argumentType);
}
The following methods of PCCActivitiesProvider are used for adding and removing
Activities. Usually, these methods are used in setupActivitySet, which is called after a
component has been initialized and while it is being activated. They can also be used during the
whole lifetime of a component, i.e. until the component is terminated.
Program 4.3-9 Adding and removing Activities in PCCActivitiesProvider
public class PCCActivitiesProvider {
/** Add an Activity to this Activities Provider, respectively its Activity Set.
* @param activity an Activity instance
* @return true, if the Activity was successfully added; false, otherwise
* (e.g., if an Activity with the same name already exists in this provider).
public boolean addActivity(PCCActivity activity) { ... }
/** Removes an Activity from this Activities Provider, respectively its
* Activity Set.
* @param name The name of the Activity to be removed
* @return the Activity removed from this Activities Provider, or null.
protected PCCActivity removeActivity(String name) { ... }
}
The following example is an excerpt from Program 4.2-1 and Program 4.2-3. SimpleTool1
implements an Activity ClipboardPasteActivity. This Activity is added in
setupActivitySet. Note that when Activities are not removed explicitly, this is done
automatically when the component terminates.
Program 4.3-10 Adding an Activity to a Simple Tool
public class SimpleTool1 extends PCCSimpleTool {
/** Set up the Tool's Activity Set which contains and manages an
* Activities-Provider's Activities.
*/
protected void setupActivitySet() {
super.setupActivitySet();
addActivity(new ClipboardPasteActivity());
}
/** A concrete Activity which is an implementation of a specific operation--in
* this case paste from clipboard.
class ClipboardPasteActivity extends PCCSingleActivity {
/** Activity constructor. In sets the dynamic type of this operation.
* Type declaration: name "clipboardPaste", category "Edit",
*
result type (void: ""), parameter types (void: "")
ClipboardPasteActivity() { super("clipboardPaste", "Edit", "", ""); }
...
}
...
}
4.3 Using Activities
65
See the following sections and Section 5.5.1 for more details about Activities. Program 4.2-3 has
a more detailed implementation of the Activity shown above.
The example below shows how an Activity can be removed.
Program 4.3-11 Removing Activities
public class SimpleTool1 extends PCCSimpleTool {
/** Remove some Activities.
*/
void removeSomeActivities() {
removeActivity("clipboardPaste");
// assuming, the Activity is stored in a member variable
// clipboardPaste of this component, the following is also possible:
// removeActivity(clipboardPaste);
}
...
}
Activities can also be added to and removed from an Activities Provider different from their
current providers (their creators). This feature can be used to implement a component which
enhances functionality of another by providing Activities for it.
When such Activities are performed at their actual provider, they have access to the context
(environment) in which they have been added.
Program 4.3-12 Adding and removing Activities through another PCCActivitiesProvider
public class PCCActivitiesProvider {
/** Add an Activity to this Activities Provider.
* @param activity an Activity instance
* @param toPath the Activity Set where the Activity should be added
* @return true, if the Activity was successfully added; false, otherwise
* (e.g., if an Activity with the same name already exists in this provider).
public boolean addActivity(String toPath, PCCActivity activity);
/** Removes an Activity from this Activities Provider.
* @param name the name of the Activity to be removed
* @param fromPath the Activity Set where the Activity should be removed
* @return the Activity removed from this Activities Provider, or null.
protected PCCActivity removeActivity(String fromPath, String name);
}
These methods can be used to add Activities to or remove from an Activities Provider different
from its actual provider. The target Activities Provider, respectively its Activity Set is specified
as path.
Once added to a different Activities Provider, the new “owner” can be retrieved via the Activity
Set currently associated with this Activity.
Program 4.3-13 Retrieving the current Activity owner
class MyActivity extends PCCSingleActivity {
M2Activity() { super("MyActivity", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
// retrieve the Activity Set, respectively the associated provider where
// this Activity is currently added to. Note that getProvider() delivers
// the actual creator of the Activity, which may be different to the current
// "owner" getActivitySet().getProvider().
PCCActivitiesProvider currentProvider = getActivitySet().getProvider();
...
// do something
return null;
}
}
66
4 Using PCoC
4.3.6 Implementing Activities
4.3.6.1 Subclassing
Program 4.3-14 shows the two methods, doPerform and doGetState, that must be
overridden in concrete Activity classes, for example, those derived from
PCCSingleActivity.
Program 4.3-14 Subclass interface of PCCActivity
/** Perform an Activity.
* It throws an exception if an unexpected error occurs during its execution.
* @param si Sender and receiver context (involved Activity Sets)
* @param arg The argument list provided for the execution
* @return the result(s) of the performed Activity
*/
protected abstract PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial arg)
throws PCCPerformException;
/** Returns the current state; is triggered by a direct call of update or
* indirectly by a call of updateActivity of the provider of the Activity. Is
* called by getState, if the state was previously invalidated with update.
* @return The current state of this Activity
*/
protected abstract String doGetState();
The following example shows an rough implementation of an Activity class for a file system
Service Provider.
Program 4.3-15 An Activity with parameters
/** A concrete Activity which is an implementation of a specific operation-in
* this case moveFile.
class MoveFileActivity extends PCCSingleActivity {
/** Activity constructor. In sets the dynamic type of this operation.
* Type declaration: name "moveFile", category "File", returns a Boolean
*
object, needs two strings, the original file path and the new one
MoveFileActivity() { super("moveFile", "File", "Boolean", "String,String"); }
/** Perform an Activity. It throws an exception if an unexpected error
* occurs during its execution.
* @param si Sender and receiver context (involved Activity Sets)
* @param arg The argument list provided for the execution
* @return the result(s) of the performed Activity
*/
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args)
throws PCCPerformException {
boolean done = moveFile(
(String)args.getObject(0),
(String)args.getObject(1)));
return new PCCMaterial(done);
}
/** Get the current state; is triggered by a direct call of update
* or indirectly by a call of updateActivity of the
* provider (component) of the Activity.
* @return The current state of this Activity
*/
protected String doGetState() {
return defaultState(true); // is always "Enabled" in this case
}
...
}
Get more about concepts and interfaces in the sections below, and in Section 5.5.
4.3 Using Activities
67
4.3.6.2 Performing Activities
The following example shows the use of the most relevant methods of Activities.
Program 4.3-16 Invoking Activity methods
PCCMaterial args = new PCCMaterial();
args.add("fromfile.txt");
args.add("tofile.txt");
PCCActivity activity = new MoveFileActivity();
String currentState = activity.getState(args);
boolean readyToPerform =
activity.canPerform(args); // is equal to getState(...) != disabledState()
Boolean result = (Boolean)activity.perform(args);
We create an Activity instance and provide arguments for the invocation of Activity methods.
getState returns the current state of the MoveFileActivity instance. canPerform is
used to check if an Activity is executable for a subsequent invocation with perform.
Note that perform and getState are template methods. The concrete behavior is defined in
derived classes by overriding the methods doPerform and doGetState. See also Section
4.3.1.
4.3.7 State Handling of Activities
The state of an Activity can be changed at any time. It represents a special attribute of an Activity
which can be used to determine whether an Activity is executable at a specific point in time, or
not.
The state can be any string. There are some predefined states:
v “Enabled”: Can be used to specify that the corresponding Activity can currently be
performed.
v “Disabled”: A specific state in which the Activity cannot be performed. The framework
ignores requests for performing Activities which are in this state.
v “Default”: This state is actually only available for the configuration. Definitions for this
state in RCTasks are used if they should be shared by concrete states. For example, if the
description of a menu item should always be “Browse Selection” no matter which
state its associated Activity has, the slot description can be defined for state “Default”. If
there is no definition for a specific state, “Default” is used instead.
Program 4.3-17 State definition in the configuration
<RCTask>
<name>Copy</name>
<dispatcher directives="First">|Application|Copy</dispatcher>
<!-- Default state. Definitions can be overridden by concrete states-->
<RCState name="Default">
<description>Copy Selection<description>
<icon>displaySelectionIcon</icon>
</RCState>
</RCTask>
This excerpt of the configuration of a menu item shows how the definitions of the Default
state is used for any state of the Dispatcher copy associated with RCTask “Copy”. The
Dispatcher represents all copy Activities of this application (path “Application”).
See Section 5.7 for a more detailed description of Tasks, and Section 5.4 for details about
Dispatchers.
68
4 Using PCoC
Program 4.3-18 Retrieving an Activity state
public class GetSelectionActivity extends PCCSingleActivity {
public GetSelectionActivity() {
super("getSelection", "SelectionCategory", "Selection", "");
}
protected String doGetState() {
return defaultState(hasSelection()); // returns "Enabled" or "Disabled"
}
...
}
The state of an Activity can be invalidated by calling update for this Activity or by calling
updateActivity in the Activities Provider where it was added.
Program 4.3-19 Invalidating an Activity state
PCCActivity activity = new GetSelectionActivity();
addActivity(activity);
activity.update();
updateActivity("getSelection");
The last two lines have the same effect. In fact, the framework retrieves in updateActivity
the given Activity and calls its update method. Listeners of activity, if there are any, are
notified in update.
Program 4.3-20 Validating an Activity state in a listener
public class MyListener implements PCCActivityListener {
public void activityChanged(PCCAbstractActivity a) {
String currentState = activity.getState();
}
public void activityAdded(PCCAbstractActivity a) {}
public void activityRemoved(PCCAbstractActivity a) {}
...
}
See Section 4.3.2 for a description of the PCCActivityListener class.
The next time when we call getState, the state is validated (in doGetState). Once the state
is validated, it is cached and used for subsequent calls of getState, until update is called
again.
Program 4.3-21 Invalidating an Activity state
public class PCCActivity extends PCCAbstractActivity {
...
public String getState(PCCMaterial arg) {
if(fState == null) { fState = doGetState(arg); }
return fState;
}
protected abstract String doGetState(PCCMaterial arg) {}
/** Update / invalidate the state of this Activity.
*/
public void update() {
resetState();
super.update();
}
void resetState() { fState = null; }
}
This code fragment illustrates the implementation of the methods update and getState. See
also Section 4.4.2.
4.3 Using Activities
69
4.3.8 Fetching Context Data of Activities
The following code fragment describes how to set context data for an Activity.
Program 4.3-22 Setting context data
public class GetSelectionActivity extends PCCSingleActivity {
public GetSelectionActivity() {
super("getSelection", "SelectionCategory", "Selection", "");
}
...
protected String doGetState() {
setContextData(/*key*/ "selection", /*value*/ getSelectionAsString());
setContextData(/*key*/ "length", /*value*/ getSelectionAsString().length());
return defaultState(hasSelection()) ;
}
}
Depending on whether there is a selection or not, the state “Enabled” or “Disabled” is
returned. The selection as string is associated with the key “selection” and the length of the
string selection is associated with key “length” in this case. The context data is valid until the
next state change. The context data can be explicitly accessed via the Activity’s interface or that
of its Dispatchers. The implementation of setContextData is shown below.
Program 4.3-23 Setting context data
public class PCCActivity extends PCCAbstractActivity {
...
public void setContextData(String key, String value) {
if (fContextData == null) { fContextData = new HashMap(1); }
fContextData.put(key.toLowerCase(), value);
}
public String getContextData() {
String contextData = "";
if (fContextData != null) {
contextData = (String) fContextData.get(key.toLowerCase());
}
if (contextData == null) { contextData = ""; }
return contextData;
}
HashMap fContextData;
}
Context data can be directly fetched from the Activity providing it, as illustrated in the following
example.
Program 4.3-24 Fetching context data
PCCMaterial args = new PCCMaterial();
PCCActivity activity = new GetSelectionActivity();
String data = activity.getContextData(args, "getSelection", "selection")
A context data entry can be referred in a context sensitive menu entry as in Program 4.3-25
(excerpt from an RCTask definition).
Context data references are enclosed by curly braces. “getFileSelection” is the Activity,
respectively the Dispatcher of the Activity, specified as argument for the actual BrowseFile
Activity in the RCTask. “selection” is the name which refers to a string defined as context
data by the Activity (the key for the context data table). <30 means that only the left 30
characters of the string representation are shown.
Context data table entries must be set when the state of an Activity changes, i.e., in
doGetState. See Program 4.3-22.
70
4 Using PCoC
Program 4.3-25 Using context data in context sensitive menus
<RCTask>
<name>BrowseFile</name>
<dispatcher directives="First">|Application|BrowseFile</dispatcher>
<args><dispatcher>getFileSelection</dispatcher></args>
<RCStates>
<RCState name="Default">
<description>Browse File<description>
<icon>browseFileSelectionIcon</icon>
</RCState>
<RCState name="Enabled">
<description>Browse '{getFileSelection:selection,<30}'<description>
</RCState>
<RCState name="Disabled">
</RCState>
<RCState name="Active">
<description>Browse '{getFileSelection:selection,<30}'<description>
<checkMarked>true</checkMarked>
</RCState>
<RCStates>
</RCTask>
The basic algorithm for retrieving context data (used with getContextData) is quite simple:
Program 4.3-26 Expanding placeholders with context data
abstract public class PCCAbstractActivity {
/** Expand a text with placeholders
* @param orgText A text containing placeholders
* @param map A map containing data to be filled into the placeholders
* @return the expanded text
*/
String expand(String orgText) {
// pseudo code
int lasttoken = 0;
int token = 0;
int septoken = 0;
int maxlen = 0;
String expTxt;
// find placeholders in the text
while ((token = orgTxt.indexOf("{", lasttoken)) < orgTxt.length()) {
expTxt.append(orgTxt.substring(lasttoken, token-lasttoken));
lasttoken = orgTxt.indexOf("}", token);
if (lasttoken < 0) { lasttoken = orgTxt.length(); }
// find length specification, if there is any
septoken = orgTxt.indexOf(",", token);
if (septoken < 0 || septoken > lasttoken) {
septoken = lasttoken;
maxlen = 0;
}
else { maxlen = Integer.valueOf(orgTxt.substring(septoken,
lasttoken-septoken-1));
}
String id = orgTxt.substring(token+1,lasttoken-token-2);
String data = expTxt.append(getContextData(id)); // get context data
if (maxlen > 0 && maxlen < data.length()) { // cut to specified length
data.substring(0, maxlen-1);
}
expTxt.append(data);
// append expanded placeholder
lasttoken++;
}
// append the rest of the string
expTxt.append(orgTxt.substring(lasttoken, orgTxt.length()-lasttoken-1))
return expTxt;
}
...
}
Read more about this topic in Section 4.4.3.
4.4 Using Dispatchers
71
4.4 Using Dispatchers
Actually, Activities are never invoked directly, but rather via Dispatchers.
Dispatchers have been introduced in Section 3.8. You can find implementation details in Section
5.6.
4.4.1 Invoking Activities
The most relevant Dispatcher methods are getState, canPerform, and perform. The
methods can either be directly called on Dispatchers (Program 4.4-1), or through convenience
methods with the same names provided by the class PCCActivitiesProvider (Program
4.4-3) or PCCActivitySet.
Let us assume, we have an Activity Set with path “|Application|AllServices|” that
acquires another Activity Set “|Application|FileSystemService|” (we also say, it
acquires its Dispatchers, respectively its Activities). FileSystemService provides two
Activities, MoveFile and CopyFile, and acquires another Activity Set. Requests on
Dispatchers in AllServices (for simplicity we only use the Activity Set name, since the full
path is not relevant here) are delegated to Dispatchers, respectively their associated Activities, in
either FileSystemService or the indirectly acquired Activity Set.
A state change through method update in the Activity MoveFile is propagated back the
delegation chain. More precisely, Dispatchers listen to state changes of their acquired
Dispatchers or their associated Activities. In the example below, first, the MoveFile Dispatcher
in the same Activity Set is notified and then the MoveFile Dispatcher in AllServices.
Subsequent calls of method getState on one of the Dispatchers lead to an update of the state
in the MoveFile Activity. See also Section 4.3.7.
Activity Set
"|Application|AllServices|"
Dispatchers
<<dispatch>>
Activity Set
"|Application|FileSystemService"
Dispatchers
MoveFile
MoveFile
CopyFile
CopyFile
Foo
...
Foo
<<state change
notification>>
<<dispatch>>
Activities
MoveFile
CopyFile
<<state change
notification>>
Activity Set
Dispatchers
<<dispatch>>
Activities
Foo
Foo
<<state change
notification>>
Figure 4.4-1 Invocation of Dispatchers
We assume, that the signature of MoveFile is Boolean MoveFile(String
fromFile,String toFile), that of CopyFile is Boolean CopyFile(String
fromFile,String toFile).
The following code fragment shows the use of Dispatchers, using the example above.
72
4 Using PCoC
Program 4.4-1 Directly calling Dispatcher methods
public class AllServices extends PCCServiceProvider {
...
public void foo() {
PCCMaterial args;
args.add("fromfile.txt");
args.add("tofile.txt");
// Note: The methods below also take an Object array for convenience:
// Object[] args = {"fromfile.txt", "tofile.txt"};
PCCDispatcher dispatcher = getDispatcher("|Application|AllServices|MoveFile");
String currentState = dispatcher.getState(
args, PCCDirectives.first());
boolean readyToPerform = dispatcher.canPerform(
args, PCCDirectives.first());
Boolean result = (Boolean)dispatcher.perform(
args, PCCDirectives.first());
String contextData = dispatcher.getContextData(
// return context data
args, "MoveFile", "selection", PCCDirectives.first()); // set in doGetState
}
In this case, Dispatchers are retrieved and stored for later use. Note that
getDispatcher(...) actually calls getActivitySet().getDispatcher(...).
Compare the code fragment to Program 4.4-3.
The parameter specified with getDispatcher is the path and name of the Dispatcher that is
used to delegate the request for performing Activities with the same name. In this case, the
execution of MoveFile is requested. We use the Dispatcher of Activity Set
“|Application|AllServices”. With the request we deliver an argument list, args. The
last
parameter
specifies
directives
for
forwarding
the
request
(e.g.
PCCDirectives.first(), PCCDirectives.broadcast(), etc.) Directives specify,
how a Dispatcher delegates requests to Dispatchers of acquired Activity Sets. More precisely,
they specify how a Dispatcher gathers Activities with the same name (“MoveFile”) of directly
and indirectly acquired Activity Sets. The specified method, for example, perform or
getState, is invoked on all resulting Activities. See Section 5.6.2 for the implementation of
the corresponding method findAllActivities. See also the direct acquisition mechanism
explained in Section 5.4.
In this example, requests (perform, getState) are delegated to a MoveFile Activity of an
Activity Set acquired by AllServices. Due to directive PCCDirectives.first(),
requests for the invocation of MoveFile are delegated to the Activity Set with the highest
priority providing an Activity with the given name (see direct acquisition in Section 5.4). In our
example, the Activity of FileSystemService is used. If there was another Activity Set
acquired by AllServices, that also provides a MoveFile Activity, the Activity Set that
most recently got the focus would be used. If directive PCCDirectives.broadcast() was
specified, requests would be delegated to both Activity Sets providing the Activity.
canPerform checks if the specified Dispatcher, respectively the corresponding Activity is
executable. It has the same effect as getState(...) != disabledState(...).
The following picture illustrates which methods are involved for an Activity request through a
Dispatcher.
4.4 Using Dispatchers
Activity Set
"|Application|AllServices|"
Activity Set
"|Application|FileSystemService"
Dispatcher
MoveFile
73
Dispatcher
<<dispatch>>
<<dispatch>>
Activity
:MoveFileActivity
MoveFile
MoveFile
<<find Activities>>
public PCCMaterial perform(
PCCMaterial args,
PCCDirectives d) {
PerformStrategy strat =
new PerformStrategy(
args, directives);
findMatchingActivities(strat);
return (PCCMaterial)strat.perform();
}
<<find Activities>>
class PerformStrategy {
Object perform() {
PCCMaterial result = null;
for (i=0; i<fActivities.size(); i++) {
PCCActivity activity =
(PCCActivity)fActivities.get(i);
activity.perform(
fSenderInfo,
fArguments);
}
return result;
}
}
public class PCCAbstractActivity {
...
public PCCMaterial perform(
PCCSenderInfo si,
PCCMaterial args) {
if (!canPerform()) { return null };
checkMaterialType(getArgumentType(),arg,...);
PCCMaterial result = doPerform(arg, si);
checkMaterialType(getReturnType(), result...);
return result;
}
}
public class MoveFileActivity
extends PCCSingleActivity{
...
public PCCMaterial doPerform(
PCCSenderInfo si,
PCCMaterial args) {
...;
return result;
}
}
Figure 4.4-2 Dispatchers delegating requests
The Activity MoveFile is an instance of the class MoveFileActivity and has been added
to the Activity Set FileSystemService. The concrete behavior is implemented in the
method doPerform, respectively doGetState. Note that we use the implementation from
Program 4.3-15.
When perform is called on Dispatcher MoveFile in AllServices (path
“|Application|AllServices|MoveFile”), we gather all Activities in the acquisition
relationship using findActivities (see Section 5.6.2 for a detailed description). In this case,
we get a list (as a member of the strategy object strat—an instance of class
PerformStrategy) with only one element—the MoveFile Activity of
FileSystemService. In the perform method of strat, we invoke the template method
perform of each Activity in the list. Finally, doPerform is called. Note that there are
different strategy classes for different requests (perform, getState, etc.).
We only describe the invocation and delegation of the perform request on a MoveFile
Dispatcher in the picture above. Other requests, such as getState, canPerform, etc., maybe
using other Dispatchers, are handled the same way.
Here an overview of requests that are supported by Activities:
Program 4.4-2 Activity methods (PCCAbstractActivity)
public boolean canPerform(PCCMaterial arg);
public String getState(PCCMaterial arg);
public PCCMaterial perform(PCCSenderInfo si, PCCMaterial arg)
throws PCCPerformException;
public String getContextData(PCCMaterial arg, String scope, String key);
Note that the corresponding methods of Dispatchers have an additional parameter holding
dispatch directives.
PCCActivitiesProvider provides methods that simplify the use of Dispatchers, if they
need not be stored for later use. The following code fragment is semantically equal to Program
4.4-1.
74
4 Using PCoC
Program 4.4-3 Calling Dispatcher methods via PCCActivitiesProvider
public class AllServices extends PCCActivitiesProvider {
...
public void foo() {
PCCMaterial args = new PCCMaterial();
args.add("fromfile.txt");
args.add("tofile.txt");
// Note: The methods below can also take an Object array for convenience:
// Object[] args {"fromfile.txt", "tofile.txt"};
String currentState = getState(
"|Application|AllServices|MoveFile",
args, PCCDirectives.first());
// directive first is the
// default and can be omitted
// canPerform is equal to getState(...) != disabledState()
boolean readyToPerform = canPerform(
"|Application|AllServices|MoveFile",
args, PCCDirectives.first());
Boolean result = (Boolean)perform(
"|Application|AllServices|MoveFile",
args, PCCDirectives.first());
String contextData = getContextData( // context data is set in doGetState
"|Application|AllServices|MoveFile",
args, "MoveFile", "selection", PCCDirectives.first());
}
}
Using these convenience methods, you save to use explicit references to Dispatchers. For
example, instead of getDispatcher(...).perform(...), we can simply write
perform(...).
4.4.2 State Handling of Dispatchers
See Section 4.3.7 for an introduction in state handling for Activities.
The following code fragment retrieves the current state of an Activity via a Dispatcher.
Program 4.4-4 Getting the state of an Activity via a Dispatcher
public class AllServices extends PCCActivitiesProvider {
...
public void foo() {
PCCDispatcher dispatcher = getDispatcher("|Application|getSelection");
String currentState = dispatcher.getState(args, PCCDirectives.first());
}
}
If the state of the Activity has been invalidated, a subsequent call of getState leads to a call of
method doGetState. If the state has not been invalidated, the cached state is used instead (the
most recent state).
4.4.3 Fetching Context Data From Dispatchers
See Section 4.3.8 for an introduction into context data retrieval for Activities.
4.4 Using Dispatchers
75
Program 4.4-5 Fetching context data via a Dispatcher
public class AllServices extends PCCActivitiesProvider {
...
public void foo() {
PCCDispatcher dispatcher = getDispatcher("|Application|getSelection");
String data = dispatcher.getContextData(null, null, "selection",
PCCDirectives.first());
data = getContextData("|Application|getSelection", null, null, "selection",
PCCDirectives.first());
}
}
In this example, we use Dispatchers to retrieve the context data entry with name “selection”
of an associated Activity. The last two statements have the same effect.
Program 4.4-6 Fetching context data via a Dispatcher
final class PCCDispatcher extends PCCAbstractActivity
implements PCCActivityListener {
...
/** Returns context data of an activity/activities.
* @param args the arguments for the activities which provide context data
* @param scope reserved. The scope is mainly used by PCCTask.
* @param key the name of a data entry in the associated Activities.
* @param directives the directives for searching activities
* @return the retrieved context data.
*/
public String getContextData(PCCMaterial args, String scope, String key,
PCCDirectives directives) {
if (directives.isBroadcast()) {
GetContextDataStrategy strat = new GetContextDataStrategy(args,
directives, scope, key);
findMatchingActivities(strat);
return (String) strat.perform();
}
else {
PCCActivity activity = findMatchingActivity(args, directives);
if (activity != null) {
return activity.getContextData(arg, scope, key);
}
return "";
}
}
}
This code fragment describes how a Dispatcher retrieves context data from associated Activities.
A sample configuration for retrieving context data from a Dispatcher for use with a context
sensitive menu item is shown in Program 4.3-25.
4.5 Using Tasks
4.5.1 Overview
In the context of this thesis a Task is a complex Activity which is composed of conditions that
must be met for execution, a Dispatcher and its argument list, and/or a list of references to other
Tasks. For example, a simple Task could be the launching of a text editor component with a
subsequent loading of a text file and the positioning of the caret at the beginning of the
corresponding text view.
Tasks are used mainly for menu entries, toolbar buttons, scripts, etc.
76
4 Using PCoC
AbstractActivity
*
fActivitySet: ActivitySet
fName: String
fCategory: String
fListeners: ArrayList
fResultType: ActivityInterface
fArgumentType: ActivityInterface
<<listener>>
0..1
Task
<<macro
entry>>
*
fDispatcher
fDispatcher: Dispatcher
fArguments: Material
fPreConditions: ArrayList
fMacro: ArrayList
fActivity
Dispatcher
fActivity: Activity
*
fDispatchers: ArrayList
<<preconditions>>
*
*
*
Activity
0..1
fDispatchers
fProvider: ActivitiesProvider
*
<<arguments>>
0..1
Material
fArguments
Object
*
fArgs: ArrayList
<<String, Integer, Float, or Boolean>>
<<arguments>>
Figure 4.5-1 Task relationships
A Task has usually a reference to a PCCDispatcher instance, and a list of arguments
represented as instance of the PCCMaterial class. The argument list can contain instances of
primitive types (String, Integer, Float, Boolean, ArrayList) as well as of more
complex types. It can also contain references to Dispatchers and Tasks which are performed and
replaced by their return value, when the container Task is performed.
A Task acts as macro, if a list of references to other Tasks is provided via configuration. See
Section 4.5.3.
The list of preconditions can contain references to Dispatchers and Tasks, which must be
executable (canPerform() == true) in order to be able to perform the Task.
The following section gives an introduction into these features. Section 5.7 has more details.
4.5.2 Simple Tasks
Program 4.5-1 shows a Task configuration in XML.
Program 4.5-1 A Task definition in a configuration
<RCTask>
<name>displaySelectionTask</name>
<dispatcher directives="First">|Application|displaySelection</dispatcher>
<args>
<dispatcher>getSelection</dispatcher>
<integer>2</integer>
</args>
<!-- Default state. Definitions can be overridden for concrete states>
<RCState name="Default">
<description>Display Selection<description>
<icon>displaySelectionIcon</icon>
</RCState>
<RCState name="Enabled">
<description>Display '{getSelection:selection,<30}'<description>
</RCState>
<RCState name="Disabled">
</RCState>
<RCState name="Active">
<description>Display '{getSelection:selection,<30}'<description>
<checkMarked>true</checkMarked>
</RCState>
</RCTask>
4.5 Using Tasks
77
Usually, Tasks are invoked through user interaction (selecting a menu entry, etc.) They can also
be used explicitly in source code (see Program 4.5-2 and Program 4.5-3).
Program 4.5-2 Performing a Task
public class MyActivitiesProvider extends PCCServiceProvider {
...
public void foo() {
// Get or create a Task; calls getActivitySet().getOrCreateTask(...);
PCCTask task = getOrCreateTask("displaySelectionTask");
if (task.canPerform()) { // if state != disabledState(), and all Dispatchers
task.perform();
// are available and executable, and all arguments
}
// are provided via the configuration, and the
// types of the passed arguments match the dynamic
}
// type of the Dispatcher
}
The Task configuration associated with the given name (“displaySelectionTask”) is used
to initialize an instance of the class PCCTask (see Program 4.5-2). See Section 5.7 for
implementation details about this class. After the Task is created, we invoke it using perform.
The method foo2 in the following code fragment is semantically equivalent to method foo of
Program 4.5-2.
Program 4.5-3 Performing a Task
public class MyActivitiesProvider extends PCCServiceProvider {
...
public void foo2() {
performTask("displaySelectionTask");
}
}
The Task can also be put into a queue using performTaskLater, where it is performed when
all other Tasks previously added to the same queue are finished.
Program 4.5-4 Adding a Task definition to a menu
<RCMenu>
<name>ExampleTasksMenu</name>
<mnemonic>E</mnemonic>
<shortDescription>ExampleTasks</shortDescription>
<items>
<item name="displaySelectionTask" type="task"/>
</items>
</RCMenu>
This example shows the same Task used with a menu item in the menu ExampleTasksMenu.
Selecting
this
menu
item
causes
the
framework
to
call
performTask("displaySelectionTask").
78
4 Using PCoC
displaySelectionTask: Task
fDispatcher = :Dispatcher
interface= void displaySelection(Selection,Integer)
fArguments= :Material
:Material
:Integer
fArgs = {:Selection, :Integer}
value = 2
<<data sink>>
displaySelection: Dispatcher
path = "|Application|displaySelection"
interface = void displaySelection(Selection,Integer)
fDispatchers = {...}
getSelection: Dispatcher
<<data source>>
path = "|Application|getSelection"
interface = Selection getSelection()
fDispatchers = {...}
Figure 4.5-2 Assembled Task
The Task of this example is associated with a Dispatcher displaySelection in Activity Set
“|Application”. This Dispatcher, and therefore also the Task, expects a Selection and
an Integer object as arguments.
When the Task is performed, it first uses the Dispatcher getSelection to invoke the Activity
getSelection, which returns a Selection object. Then the Task uses the Dispatcher
displaySelection of Activity Set Application to request the invocation of method
perform on an “acquired” Activity. It passes the previously retrieved selection object and an
Integer object with value 2 as arguments. The arguments are passed via an instance of class
PCCMaterial by the framework.
Since the getSelection Dispatcher does not have a path in this example, the Dispatcher of
the Activity Set where the current Task object has been created, is used. This means that a
component that creates and performs a corresponding Task object, and which provides a
getSelection Activity, would pass its own selection object to this Task. The target
component (the component providing the displaySelection Activity) is determined
through the acquisition graph beginning with the Application Activity Set. A target
component, respectively its Activity Set is found if it has directly or indirectly been acquired by
the Application Activity Set and provides the Activity displaySelection, and the
acquisition branch to it has a higher priority than those of other Activity Sets possibly providing
this Activity.
If no Activities displaySelection and getSelection are created and acquired, yet, the
whole Task is not executable. When the Dispatchers displaySelection and
getSelection
are
available,
respectively
corresponding
Activities,
and
displaySelection has the state “Enabled”, but getSelection has the state “Disabled”,
then the Task displaySelectionTask is valid, but cannot be performed due to the
“Disabled” state of getSelection.
A list of preconditions and the dispatcher directive also regulate whether a Task can be
performed. A condition can itself be a Task or a Dispatcher that must be available and
executable.
4.5 Using Tasks
79
Program 4.5-5 Defining a Task via configuration
<RCTask>
<name>displaySelectionTask</name>
...
<preconditions>
<dispatcher>|Application|foo</dispatcher>
<task>myCondition</task>
</preconditions>
</RCTask>
<RCTask>
<name>myCondition</name>
...
</RCTask>
In this case, the Dispatcher foo and the Task myCondition must be executable
(canPerform must return true for each condition). Only if this is true, the Task
displaySelectionTask can be performed.
See also Section 5.7 for implementation details of Tasks.
4.5.3 Macros
Tasks can be used as macros. Therefore they provide a slot do which can contain a list of
references to other Task definitions or inlined Task definitions. When a macro-Task is
performed, the Tasks referenced in this list are performed in the given order.
<<initialize>>
Configuration
task1: Task
<<macro>>
fooMacro: Task
path = "|Application|fooMacro"
fMacro: ArrayList = {...}
task1 :Task
path = "|Application|task1"
fDispatcher = :Dispatcher
fArguments = :Material
displayFilteredSelection: Dispatcher
<<arguments>>
:Material
path = "|Application|displayFilteredSelection"
interface = boolean displayFilteredSelection(
Selection, String, Integer)
task2 :Task
value = {:Dispatcher, 2, "foo"}
task3 :Task
task4 :Task
getSelection: Dispatcher
:Integer
:String
path = "|Application|getSelection"
interface = Selection doSomething(void)
value = 2
value = "foo"
task3: Task
<<macro>>
task4: Task
task2: Task
path = "|Application|task3"
fDispatcher = :Dispatcher
fArguments = :Material
path = "|Application|task4"
fMacro: ArrayList = {...}
path = "|Application|task2"
fDispatcher = :Dispatcher
fArguments: Material = null
doSomething: Dispatcher
path = "|Application|doSomething"
interface = void doSomething(void)
Figure 4.5-3 Assembling a Task
Figure 4.5-3 shows a macro fooMacro. The configuration may look as in Program 4.5-6.
The first two entries in the do-list, task1 and task2, are inlined Tasks. The other two, task3
and task4, are references to other Task definitions.
Note that definitions can be distributed over several files.
task1 is associated with a Dispatcher displayFilteredSelection in Activity Set
Application. A Dispatcher (getSelection), a string (“foo”) and an integer (2) are
task1,
passed
as
arguments
to
respectively
its
associated
Dispatcher
displayFilteredSelection.
task2 is associated with Dispatcher doSomething and has no arguments. Since no Activity
Set path is specified for doSomething, the Activity Set of the Task is used (Application).
We assume that task3 is no macro, and task4 is a macro-Task.
80
4 Using PCoC
Program 4.5-6 Defining a macro (script)
<RCTask>
<name>fooMacro</name>
<do>
<RCTask>
<name>task1</name>
<dispatcher directives="First">
|Application|displayFilteredSelection</dispatcher>
<args>
<dispatcher>|Application|getSelection</dispatcher>
<string>foo</string>
<integer>2</integer>
</args>
</RCTask>
<RCTask>
<name>task2</name>
<dispatcher directives="First">doSomething</dispatcher>
</RCTask>
<task>task3</task>
<task>task4</task>
</do>
</RCTask>
<RCTask>
<name>task3</name>
...
</RCTask>
<RCTask>
<name>task4</name>
...
<do>
...
</do>
</RCTask>
When fooMacro is performed, task1 is performed first. On successful completion of task1,
task2 is performed, etc. If a Task of the macro cannot be completed successfully, the execution
of the macro is stopped per default. That is, the subsequent Tasks in the do-list are not
performed.
You can change this behavior by setting the stopOnError-flag correspondingly.
Program 4.5-7 Defining a macro (script)
<RCTask>
<name>fooMacro</name>
<stopOnError>0</stopOnError>
<do>
<RCTask>
<name>task1</name>
...
Note, that, when a macro-Task is invoked, all paths of referenced Dispatchers are temporarily
replaced by the paths of concrete Activities right before it is actually executed. This prevents
unexpected behavior, if the acquisition priorities (the ranking) of Activity Sets change during the
execution of the macro, for example, if a new component is loaded or removed during the
execution, or the ranking is changed explicitly.
Let us assume that the Activity Set with path “|Application|MyView” provides an Activity
displayFilteredSelection. The Activity Set is acquired by Application and has the
highest ranking in the list of parents in Application. In this case, the Dispatcher path
“|Application|displayFilteredSelection” will be replaced by the path
“|Application|MyView|displayFilteredSelection”.
4.5 Using Tasks
81
Here the relationships of some Dispatchers in the macro when it is invoked:
Activity Set
"|Application"
<<Task>>
fooMacro
Activity Set
"|Application|MyView"
<<Task>>
task1
<<Dispatcher>>
displayFilteredSelection
<<Dispatcher>>
displayFilteredSelection
<<Activity>>
displayFilteredSelection
Figure 4.5-4 Replacing Dispatchers paths (1)
task1 uses the displayFilteredSelection Dispatcher of Activity Set Application.
The following picture illustrates the relationships after replacing the Dispatcher paths before the
actual execution of fooMacro.
Activity Set
"|Application"
<<Task>>
fooMacro
Activity Set
"|Application|MyView"
<<Task>>
task1
<<Dispatcher>>
displayFilteredSelection
<<Dispatcher>>
displayFilteredSelection
<<Activity>>
displayFilteredSelection
Figure 4.5-5 Replacing Dispatcher paths (2)
Now, task1 directly uses the displayFilteredSelection Dispatcher of Activity Set
MyView.
The macro may then look as follows:
Program 4.5-8 Defining a macro (script)
<RCTask>
<name>fooMacro</name>
<do>
<RCTask>
<name>task1</name>
<dispatcher directives="First">
|Application|MyView|displayFilteredSelection</dispatcher>
<args>
<dispatcher>
|Application|TextEditor[0]|TextEditorTool[0]|getSelection
</dispatcher>
<string>foo</string>
...
Note that the macro is only changed temporarily until the execution has ended. The change has
no impact on the original configuration.
How a Dispatcher determines its currently associated Activity or Activities, is described in 5.6.2.
Normally, a Dispatcher gathers acquired Activities (depending on the specified directives) and
invokes methods on them, but in this case only their paths are determined and finally used as
Dispatcher paths in the macro. So, when the macro is actually executed, the same Dispatchers are
used, even if the ranking has changed.
For example, if the Activity Set “|Application|MyView2|displayFilteredSelection”
gets the highest priority in “|Application” during the execution of the macro, the
displayFilteredSelection Dispatcher will still be used from Activity Set
“|Application|MyView”. See below.
82
4 Using PCoC
Activity Set
"|Application"
<<Task>>
fooMacro
Activity Set
"|Application|MyView"
<<Task>>
task1
<<Dispatcher>>
displayFilteredSelection
<<Dispatcher>>
displayFilteredSelection
<<Activity>>
displayFilteredSelection
Activity Set
"|Application|MyView2"
<<Dispatcher>>
displayFilteredSelection
<<Activity>>
displayFilteredSelection
Figure 4.5-6 Replacing Dispatcher paths (3)
4.6 Delegation using PCoC
As we learned in Section 2.2.4, delegation is a dispatch mechanism for object composition.
The overhead for managing (class-based) method dispatch for implementation inheritance is
relatively low. The concept is very mature. However, object composition has some advantages
compared to implementation inheritance. We get the flexibility to compose object sets at
runtime, which increases the level of reusability of single objects, and which adds more
dynamics to a system. However, object composition usually does not support (object-based)
delegation, but only forwarding. In order to accomplish delegation across different objects, we
can make the forwarding mechanism stronger. That is what PCoC tries to achieve—a delegation
mechanism (using Dispatchers) that passes information about the Activity Set (the context) in
which an Activity is requested, as separate parameter to the Activity.
Program 4.6-1 shows an implementation of a class C as PCoC Service Provider. We define class
C and the Activities M1 and M3. M1 calls M2 which may be defined in another provider. M3
executes some code. Note that the actual implementation of doPerform is not relevant here.
Program 4.6-1 Delegation using PCoC (1)
public class C extends PCCServiceProvider {
public C() {
activate(); // creates an Activity Set with the class name
// ("C") as name. Use setType("<typename>") for setting another
}
// name, or use constructor of the base class:
super("<typename>")
protected void setupActivitySet() {
super.setupActivitySet();
addActivity(new M1Activity());
addActivity(new M3Activity());
}
class M1Activity extends PCCSingleActivity {
M1Activity() { super("M1", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
...
// do something
// invoke M2 in the original context
si.getReceiver().perform("M2", null, PCCDirectives.first());
return null;
}
}
class M3Activity extends PCCSingleActivity {
M3Activity() { super("M3", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
... // do something
return null;
}
}
}
4.6 Delegation using PCoC
83
Now that we have Activities Provider C with Activities M1 and M3, we also need an Activities
Provider CB which defines the Activity M2.
Program 4.6-2 Delegation using PCoC (2)
public class CB extends PCCServiceProvider {
public CB() {
activate(); // creates an Activity Set (Activity Set) with the class name
// ("CB") as name. Use setType("<typename>") for setting another
// name, or use constructor of the base class: super("<typename>")
}
protected void setupActivitySet() {
super.setupActivitySet();
addActivity(new M2Activity());
}
class M2Activity extends PCCSingleActivity {
M2Activity() { super("M2", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
...
// do something
// invoke M3 in the original context
si.getReceiver().perform("M3", null, PCCDirectives.first());
return null;
}
}
}
Now the Activity M2 is also defined. It invokes an Activity M3 in the context of the original
receiver—the Activity Set where M2 was invoked (si.getReceiver()).
The following client code creates a delegation relationship by connecting the Activities Providers
from above.
Program 4.6-3 Delegation using PCoC (client code)
/** create components */
PCCActivitiesProvider c = new C();
PCCActivitiesProvider cb = new CB();
/** create composite context */
PCCActivitySet context = PCCRegistry.getOrCreateActivitySet("|CombinedSet");
context.acquire(c.getActivitySet());
context.acquire(cb.getActivitySet());
/** perform (invoke) the operation M1 */
context.perform("M1", null, PCCDirectives.first());
We instantiate two Activities Providers, c and cb. Then, we create an Activity Set
“CombinedSet” that acquires from c and cb, respectively their Activity Sets. CombinedSet
represents a common “self” (we say, a common context) for the acquired Activity Sets. The
perform method of Activity Set context is used to invoke Activity M1, which is in this case
provided by c. We say, the request for executing Activity M1 is delegated to c. M1 invokes M2
(as shown in Program 4.6-1). This request will be delegated to cb. M2 subsequently invokes M3
on the Activity Set context where the first Activity (M1) in the delegation sequence was
invoked (see Program 4.6-2). context was the original receiver of the M1 request, and then of
the M2 request.
If c directly acquires from cb, we need no separate Activity Set to specify a common context. In
this case, c, or more precisely its Activity Set, provides the common context for c and cb.
84
4 Using PCoC
Program 4.6-4 Delegation using PCoC (client code)
PCCActivitiesProvider c = new C();
PCCActivitiesProvider cb = new CB();
c.add(cb); // add child; or call c.acquire(cb) directly without adding cb as child
c.perform("M1", null, PCCDirectives.first());
In this example, cb is added as element to container c (i.e., the Activity Set path of cb is
contained in that of c), and is also acquired by c. If cb needs not be an element of c, we can also
directly call c.acquire(cb). In both cases, c becomes the delegation child of cb.
We invoke Activity M1 by using Activities Provider c. The delegation sequence is the same as in
Program 4.6-3.
We can use the same mechanism for simple forwarding, simply by not using the sender info
parameter. Instead, we can use the current Activity Set (the current owner of the Activity) within
an Activity. Compare the following code fragment to Program 4.6-2.
Program 4.6-5 Delegation using PCoC (3)
public class CB extends PCCServiceProvider {
...
class M2Activity extends PCCSingleActivity {
M2Activity() { super("M2", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
...
// do something
// invoke M3 in the same Activity Set; corresponds to
// getActivitySet().perform("M3", null, PCCDirectives.first())
perform("M3", null, PCCDirectives.first());
return null;
}
}
}
In this case, the execution of Activity M3 by Activity M2 will fail, since M3 is neither provided,
nor acquired by Activity Set CB (the current owner of Activity M2). However, we can
nevertheless request Activities using the registry.
Program 4.6-6 Delegation using PCoC (4)
public class CB extends PCCServiceProvider {
...
class M2Activity extends PCCSingleActivity {
M2Activity() {
super("M2", "Methods", "", "");
}
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
...
// do something
// invoke M3 in the given Activity Set
PCCRegistry.getActivitySet("|CB").perform("M3", null,
PCCDirectives.first());
return null;
}
}
}
4.6 Delegation using PCoC
85
Activities can be provided by one Activity Set, but added to another.
Program 4.6-7 Delegation using PCoC (5)
public class CB extends PCCServiceProvider {
public CB() {
activate(); // creates an Activity Set (Activity Set) with the class name
// ("CB") as name. Use setType("<typename>") for setting another
// name, or use constructor of the base class:
super("<typename>")
}
protected void setupActivitySet() {
super.setupActivitySet();
addActivity("C", new M2Activity()); // add Activity to Activity Set "C", but
// its provider is the current ("CB")
addActivity(new M4Activity()); // add Activity to the current Activity Set
}
class M2Activity extends PCCSingleActivity {
M2Activity() { super("M2", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
...
// do something
}
}
class M4Activity extends PCCSingleActivity {
M2Activity() { super("M4", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
...
// do something
}
}
}
In this case, the Activity M2 is added to Activity Set C (the Activity Set associated with Activities
Provider C). However, it will be performed in CB. We say, we enhance C by an Activity of
Activities Provider CB. This is useful, for example, if we do not have access to the source code
of C. See Program 4.3-12 for a description of the addActivity method.
Program 4.6-8 Delegation using PCoC (client code)
/** create components */
PCCActivitiesProvider c = new C();
PCCActivitiesProvider cb = new CB();
/** perform (invoke) the operation M1 */
c.perform("M1", null, PCCDirectives.first());
Since Activity M2 is already added to C, is not necessary that C acquires CB. Compare to
Program 4.6-3 and Program 4.6-4. The advantage of this approach is, as opposed to acquisition,
that M2 has access to both Activity Sets—the one where it has been added, and that of its
provider (which may implement other Activities or methods necessary for the execution of M2).
Program 4.6-9 Delegation using PCoC (5)
public class CB extends PCCServiceProvider {
...
class M2Activity extends PCCSingleActivity {
M2Activity() { super("M2", "Methods", "", ""); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args) {
// invoke M3 in the Activity Set,where this Activity has been added ("C")
getActivitySet().perform("M3", null, PCCDirectives.first());
// invoke M4 in the Activities Provider, respectively Activity Set,
// where this Activity has been created ("CB")
getProvider().perform("M4", null, PCCDirectives.first());
return null;
}
}
}
86
4 Using PCoC
Note that, as long as there is a reference to a single Activities Provider, all related objects such as
Activities, Dispatchers, (acquired) Activity Sets, etc., are prevented from being garbage
collected. In order to clean up and remove an Activities Provider, terminate has to be called.
Its Activity Set will be unregistered from the PCoC registry and released for later cleanup
together with all related objects.
Program 4.6-10 Cleaning up Activities Providers
cb.terminate(); //
//
//
c.terminate(); //
terminates cb, removes its Activities, and discards its
Dispatchers from acquiring Activity Sets (in this case,
that of c)
terminates c
Delegation is discussed in more detail in [SZYPE98] on Pages 119ff. Other related pages are
155f (Events and messages), 159ff (Very late binding: dispatch interfaces and metaprogramming), and 324ff (On the horizon). [GRIFF98] also describes common issues of
delegation on Pages 254ff (Event channels), and 424ff (Delegation).
Apart from dynamic and late object composition, the availability of delegation is also a
requirement of prototypical programming languages. Read more about delegation in prototypical
systems in [LIEBER86].
5 Detailed Concepts and Implementation
87
5 Detailed Concepts and Implementation
This chapter explains the design and most essential concepts of PCoC.
5.1 PCoC-Related Packages and Responsibilities
This section describes dependencies between PCoC and other packages, as well as the
responsibilities of these packages.
The packages are not described in detail, and there may be other required features that have not
yet been considered. However, this section may help to get an overview of how PCoC is
structured.
We have chosen the following set of packages to build a PCoC application:
PCoC
Other Services
Data Services
Frame Manager
JNI
Tools
Threading
Service Providers
Configuration
XML reader/writer
Application interfaces
Widgets
Painters
Views
Trees
Models
Tables
Graphs
Figure 5.1-1 Overall Architecture
PCoC includes Tools, Service Providers, a configuration library (reading and writing data
structures used to customize Activities Providers and a whole application), and a frame manager
responsible for menu and toolbar setup, for window management, and for composing, starting up
(initialize), and terminating Activities Providers.
Widget libraries include view classes for tables, trees, and graphs, and proper data models and
painters.
Other services are data services (general data models and access operations), a threading model,
and component and application interfaces.
Figure 5.1-2 illustrates how these packages are related to each other. There may be changes in
detail, e.g. the package structure, implementation details, etc. This does not have an impact on
the overall design.
88
5 Detailed Concepts and Implementation
client component
FrameManager
package: pcoc.fm
classes: FMFramemanager,
FMWindow,
FMRegion,
etc.
<<uses>>
<<uses>>
<<uses>>
<<uses>>
CommandManager
package: pcoc.cm
classes: PCCMenuMaker,
etc.
<<uses>>
<<uses>>
<<uses>>
PCoC core
package: pcoc.core
classes: PCCAbstractActivity,
PCCActivity,
PCCDispatcherImpl,
PCCDispatcher,
PCCActivitySet,
PCCActivitiesProvider,
PCCRegistry,
etc.
<<uses>>
clients can derive
from a StdTool of
the StdWidgets package,
or directly from a class of
the PCoC package:
PCCSimpleTool for GUI
components, or
PCCServiceProvider for
non-GUI components
(service components)
StdWidgets / StdTools
<<uses>>
Tables
Graphs
<<uses>>
Editor
<<uses>>
Configuration
package: pcoc.rescfg
classes: ResConfigMap,
ResIdMap,
etc.
Figure 5.1-2 PCoC and related packages
Packages and classes, as shown in Figure 5.1-2, have only dependencies in one direction. This
layered design with only one-way dependencies makes it easy to make modifications
(extensions) to packages, or to replace them by other implementations without having an impact
on the packages they use.
5.1.1 Configuration
The configuration, respectively the Configuration Manager, is used to customize the look and
feel of an application. This includes layout definitions for the whole application and its Tools,
definitions for menus and toolbars and their associated Tasks, etc.
A central XML-based configuration file is used as root configuration, and can import others.
Preferably each Activities Provider has its own configuration file. The configuration (all
configuration files of an application) can be reloaded at runtime. Definitions are loaded from the
configuration files into a hash map (ResConfigMap) where they can be looked up by name.
Another map (ResIdMap) allows the lookup with integer keys. For example, menu items may
be associated with integer keys instead of strings.
5.1.2 Frame Manager
The Frame Manager is responsible for the window creation, layout management (positioning of
windows, cascaded or tiled layout, storing and reloading layout information), region management
(repainting of invalidated regions), focus management (set focus and caret according to user
input), status line management (switching displayed information on focus changes), drag and
drop management.
5.1 PCoC-Related Packages and Responsibilities
89
5.1.3 Command Manager
The command manager is responsible for menu and toolbar management (setting up and
updating), accelerator keys handling, and command management (handling do, undo, and redo
operations).
5.1.4 PCoC Core
The PCoC core package contains almost all classes relevant for this thesis. This includes Activity
classes (abstract base class for all kinds of Activities, and base classes for deriving in client
code), Dispatcher and DispatcherImpl (the proxy and its implementation), Task and TaskQueue
(a queue for the asynchronous execution of Tasks), Material (argument container class), Activity
Set and Activity Set Registry, and the Activities Provider class.
5.1.5 Simple and Combined Tools
The base classes for client components, PCCSimpleTool, and PCCServiceProvider can
be found in the PCoC root package (pcoc), as well as the PCCCombinedTool class (the
generic container for Simple Tools).
5.1.6 Standard Widgets / Standard Tools
Standard Widgets are GUI elements (e.g. edit views, table views, graph views, etc.) that are
needed to make templates for most common Tools—so-called Standard Tools. A Standard Tool
is a Simple Tool containing a table, tree, graph, or editor. Such a Tool, respectively its template
implementation (base classes), is assembled from a data model, a view, painters, etc.
The main purpose of Standard Widgets and Standard Tools is to provide uniform and basic
implementations of common Tools. This reduces implementation and maintenance time and
effort for this kind of component. A component developer can concentrate on implementing the
actual functionality of his component, instead of creating solutions for common issues from
scratch.
5.1.7 Remarks
We have chosen this architecture, because (a) it allows clear separation of responsibilities and (b)
dependencies are uni-directional. This modular (layered) architecture enables us to make
modifications to packages, or completely replace them by others, without having an impact on
packages they use. See Figure 5.1-1. This is the software design recommended in the technical
literature.
Higher layers use those below and “pull” information from them. Packages on a lower layer may
provide extensions for higher layer packages, for example, abstract base classes, template
methods, listener interfaces, etc. This can keep the dependencies clear and uni-directional and
keeps the maintenance effort low.
5.2 Activities Provider
Activities Providers are components based on the PCoC framework. Figure 5.2-1 illustrates how
all PCoC classes are related to each other, and describes their meanings. The elements shown in
grey are those implemented by component developers. Others are created automatically, such as
Dispatchers, or generated depending on the configuration, such as Tasks.
MultiActivity
Such Activity invocation requests are always
forwarded by one or many Dispatchers to a
concrete Activity anywhere in the system,
depending on the Activity Set priorities
and forwarding directives.
If an Activity is performed, it usually calls,
possibly virtual, methods of its
Activities Provider. It can also request the invocation
of other Activities in the system via invocation of
Dispatchers or Tasks.
If directly using a Dispatcher, call:
perform("|Application|Copy") or
perform("|Application|Editor|Copy").
For example, use a Task, which is a
autonomous unit consisting of a combination of
Dispatchers and arguments.
It can be used for invocation of single Activities
and scripts. performTask("Copy"),
where Copy would invoke a Dispatcher
|Application|Copy which belongs to
Activity Set Application and dispatches the
request to a concrete Activity—for example Activity
Copy of Activity Set |Application|Editor.
an Activities Provider creates Activities as
inner or anonymous classes. It can request the
invocation of an Activity by invoking a Task or
Dispatcher.
SingleActivity
Activity
for
general
use.
*
prioritized
forwarding
(distribution)
*
task
*
1
an Activities Provider
providing specific
services via Activities.
*
<<datamodel>>
1
container
element
1
0..1
*
1
*
*
Create or update
Dispatcher when
an Activity is
added to the
Activity Set
1
activitySet
1
basic services,
e.g.,
file system
operations
<<service>>
*
<<use>>
*
1
provider
Generically
created from
definition in
the component
configuration
CombinedTool
Activities Provider
1 configuration
1
*
*
*
<<datamodel>>
*
1
<<presentation>>
(view)
0..1
0..1
SimpleTool
*
Two ways to implement:
- its own class
or
- a composition created
by the Tool
Activities Providers
providing specific services
via Activities and having a
presentation--a graphical
or character based
user interface.
Tool
the name service
used for loosely coupling
Activities Providers, providing
hierarchical structuring,
acquisition, and priority
management at runtime.
adds the acquired Activity Set
to an ordered list and its
Dispatchers to Dispatchers of
the acquiring Activity Set.
The ordered list serves for
prioritized delegation of Activity
requests by Dispatchers.
If an Activity Set is focussed, it
is moved to first place in the
ordered list of the acquiring
Activity Set, thus giving it,
respectively its Activities Provider,
the highest priority.
Activities Provider (component)
acquiredSets
{ordered} <<acquire>>
component: a unit that
can be deployed
independently of its
environment. It contains
and manages necessary
properties and resources.
<<instantiation>>
1
<<instantiation>>
activitySet
1
1
container
Activity Set
1
persistent storage
(for toolbars, menus,
etc.) or temporary
(for user code)
<<instantiation>>
<<configuration>>
(resources, properties)
for each
local or acquired
Activity
<<instantiation>>
delegates
Activity invocation
requests,
using directives
configuration
0..1
<<component configuration>>
(resources, properties)
Activity
provider
*
1..*
Dispatcher
*
1
*
Task
<<instantiation>>
ServiceProvider
ServiceProvider
1
*
macro <<script>>
an operation implemented
as class. Has a state which can
be tested before execution,
e.g. Enabled or Disabled
AbstractActivity
represents an arbitrary
number of Activities of the
same dynamic type.
The actual number is
determined and can be changed
at runtime. Used for
dynamic utilities like history
menus, where the number and
contents of entries are dynamic.
MappedActivity
a proxy for and
adapter to a Task
with a possibly
other interface.
Is usually created
automatically using
definitions in the
configuration.
synchronous or
asynchronous single or
composite (script) Activity
with preconditions.
Used within sourcecode or
generically via configuration
for toolbars and menus
90
5 Detailed Concepts and Implementation
Figure 5.2-1 Activities Provider Architecture
The picture should be self-explanatory. Please read the notes. Note that, when an Activity is
created and added to an Activity Set, the Activity Set automatically creates and adds a
corresponding Dispatcher.
5.3 Containment Hierarchy
91
5.3 Containment Hierarchy
Activities Providers (PCoC components), respectively their Activity Sets can be organized in a
logical containment hierarchy, like a package hierarchy in Java or a directory tree in file system.
Containment hierarchies may depend (partially) on the hierarchy of GUI elements.
<<is container of>>
*
Activities Provider
1
Figure 5.3-1 Containment hierarchy
Figure 5.3-1 shows the containment relationship of Activities Providers. An Activities Provider
can be the container of no, one, or many others.
The nodes in a containment hierarchy are distinguished by path. A path is, for example, used to
lookup Activity Sets in the PCoC registry, and to lookup Activities, Dispatchers, or Tasks via
Activity Sets. Paths are used case insensitively in PCoC.
We use “|” as path separator. Paths starting with character “|” are absolute paths.
Program 5.3-1 Using an absolute Activity Set path
PCCRegistry.getActivitySet("|Application").perform("|Application|Copy", null,
PCCDirectives.first())
In this case, we invoke the Dispatcher Copy in the Activity Set associated with path
“|Application”.
If the first character is not “|”, the path is considered relative to the path of the current Activity
Set. For example, calling the method perform with path “TextEditor|Copy” in an
Activity Set “|Application” is resolved to “|Application|TextEditor|Copy”.
Program 5.3-2 Using a relative Activity Set path
PCCRegistry.getActivitySet("|Application").perform("TextEditor[0]|Copy", null,
PCCDirectives.first())
5.3.1 Sample Hierarchy
A containment hierarchy is defined by the paths of Activities Providers, respectively their
Activity Sets. Compare this to a directory tree in a file system.
Figure 5.3-2 depicts a realistic containment hierarchy for an IDE application. The Activity Sets
associated with the given Activities Providers are organized accordingly. See Figure 3.4-2 and
Figure 3.4-3 for screen shots.
92
5 Detailed Concepts and Implementation
:Application
path = "|Application"
+container
<<is container of>>
+container
<<is container of>>
<<is container of>>
+element
+element
ProjectManager: CombinedTool
:FileSystemService
TextEditor: CombinedTool
path = "|Application|ProjectManager[0]"
path = "|Application|FileSystemService"
path = "|Application|TextEditor[0]"
+container
+container
<<is container of>>
+element
+container
<<is container of>>
<<is container of>>
+element
+element
:ProjectBrowserTool
:FileSelectionTool
path = "|Application|ProjectManager[0]|
ProjectBrowserTool[0]"
path = "|Application|ProjectManager[0]|
FileSelectionTool[0]"
:TextEditorTool
path = "|Application|TextEditor[0]|
TextEditorTool[0]"
Figure 5.3-2 A possible containment hierarchy
In our example, a text editor component (a class derived from PCCSimpleTool, respectively
its instance) has the path “|Application|TextEditor[0]|TextEditorTool[0]”. It
is a child of the Combined Tool TextEditor[0].
Service Provider FileSystemService is child of the root Service Provider Application,
therefore it has the path “|Application|FileSystemService”.
Note that indexes (for example, in TextEditorTool[0]) are automatically created by the
framework in order to get unique names for the Activity Sets of multiple instances of Activities
Providers. Activities Providers have a method getPath which returns their current path.
5.3.2 Modifying a Containment Hierarchy
Activities Providers can be added manually to the containment hierarchy by specifying the
container object or path.
Program 5.3-3 Methods for setting a container
void setContainer(PCCActivitiesProvider container);//
void setContainer(PCCActivitySet container);
//
void setContainer(String container);
//
//
from PCCActivitiesProvider
from PCCActivitySet
from PCCActivitiesProvider
and PCCActivitySet
The following code fragment illustrates the use of these methods:
Program 5.3-4 Setting a container
PCCActivitiesProvider a = new PCCActivitiesProvider("A");
PCCActivitiesProvider b = new PCCActivitiesProvider("B");
b.setContainer(a); // is implicitly called when using a.add(b)
In this case, the Activities Provider b becomes the container of a. The containment relation can
also be created via the corresponding Activity Sets. This is useful, if we only have the current
paths of the Activities Providers, or some Activities Providers are not instantiated yet.
5.3 Containment Hierarchy
93
Program 5.3-5 Setting a container via paths
PCCActivitySet a = PCCRegistry.getActivitySet("|A");
PCCActivitiesProvider b = new PCCActivitiesProvider("B");
b.activate(); // do some initialization
b.getActivitySet().setContainer(a); // register "B" as element of "A"
b.setContainer("|A");
// this statement has the same effect as that above
Now b has the path “|A|B”. Next, we can retrieve the Activities Provider b through the registry:
Program 5.3-6 Retrieving an Activities Provider via the registry
PCCActivitiesProvider b = PCCRegistry.getActivitySet("|A|B").getProvider();
// or simpler:
PCCActivitiesProvider b = PCCRegistry.getActivitiesProvider("|A|B");
Now, let us take a look at an implementation of a simple Tool.
Program 5.3-7 SimpleTool2.java (1)
package example2;
import pcoc.tools.*;
import javax.swing.*;
/** A Tool providing and displaying a list of strings. */
public class SimpleTool2 extends PCCSimpleTool {
JList fList;
/** The constructor. Adds a service provider as element. */
public SimpleTool2() {
PCCActivitiesProvider childService = new SampleServiceProvider();
add(childService);
// calls childService.setContainer(this)
}
...
Program 5.3-7 shows how a Simple Tool creates and adds a Service Provider as child. The Tool's
visual representation and its Activity Set may be defined as follows:
Program 5.3-8 SimpleTool2.java (2)
/** Set up the Tool's GUI
* @return A JComponent representing the GUI of this Tool
*/
public JComponent makePresentation() {
String [] data = { "Hello World!" };
fList = new JList(data);
return new JScrollPane(fList);
}
/** Set up the Tool's Activity Set */
protected void setupActivitySet() {
super.setupActivitySet();
...
}
...
}
Since our Tool has no container in this case, it gets the Activity Set path “|SimpleTool2”.
When this Tool is added to the GUI hierarchy, it may get, depending on the configuration (see
below), for example, the path
“|Application|CombinedTool2[0]|SimpleTool2[0]”.
94
5 Detailed Concepts and Implementation
Program 5.3-9 Component configuration
<?xml version = "1.0" encoding = "UTF-8"?>
<ResConfig name="Example1" xsi:schemaLocation="www.windriver.com/ResConfig
file:///pwe/rome/config/tools/ResConfig.xsd"
xmlns="www.windriver.com/ResConfig"
xmlns:xsi="http://www.w3.org/2000/10/XMLSchema-instance">
...
<RCSimpleTool>
<name>SimpleTool2</name>
<className>example2.SimpleTool2</className>
</RCSimpleTool>
<RCCombinedTool>
<name>CombinedTool2</name>
<windowTitle><dockedTitle>My Combined Tool</dockedTitle></windowTitle>
<part><simpleToolName>SimpleTool2</simpleToolName></part>
</RCCombinedTool>
...
The following code fragment describes the class SampleServiceProvider. It is initialized
with the Activity Set name “SampleService”, that is, it can be retrieved with this name
through the Activity Set registry.
Program 5.3-10 SampleServiceProvider.java
package example3;
import pcoc.*;
import javax.swing.*;
/** A Service Provider providing specific services (not implemented here)
*/
public class SampleServiceProvider extends PCCServiceProvider {
/** The constructor setting the Activities Provider associated with path
* "|Application|AService" as parent.
public SampleServiceProvider() {
super("SampleService");
activate();
}
...
}
The Service Provider added as childService in Program 5.3-7 is then accessible with path
“|Application|CombinedTool2[0]|SimpleTool2[0]|SampleService”.
The container and thus the path of the corresponding Activity Set can be changed dynamically.
See below:
Program 5.3-11 SampleServiceProvider.java
package example3;
import pcoc.*;
import javax.swing.*;
/** A Service Provider providing specific services (not implemented here)
*/
public class SampleServiceProvider extends PCCServiceProvider {
...
public void foo() {
PCCActivitiesProvider containerComponent =
PCCRegistry.getActivitiesProvider("|Application|AllServices");
containerComponent.add(this);// calls this.setContainer(containerComponent)
}
}
5.3 Containment Hierarchy
95
The Service Provider can then be looked up via the PCoC registry:
Program 5.3-12 Retrieving a Service Provider via the registry
PCCServiceProvider sampleService;
sampleService = (PCCServiceProvider)
PCCRegistry.getActivitiesProvider(
"|Application|AllServices|SampleService");
sampleService = (PCCServiceProvider)
PCCRegistry.getActivitySet(
"|Application|AllServices|SampleService").getProvider();
Both statements have the same effect.
5.4 Acquisition: Dynamic Component Inheritance
This section roughly describes the environmental acquisition concept, and provides detailed
insight into the PCoC acquisition concept.
5.4.1 Overview
Acquisition is a delegation mechanism that lets objects and components acquire functionality
from others at runtime. As opposed to class inheritance, attributes and operations can be
distributed over various objects or components, independent of the class hierarchy.
There are two forms of acquisition:
v Environmental acquisition: A mechanism that allows an object to obtain a requested feature
from its environment (its parents in a containment hierarchy), if neither its class nor its base
class provides the feature. There are two styles of environmental acquisition, implicit
(automatic) and explicit acquisition.
v Direct acquisition: A mechanism that allows objects or components to acquire (dynamically
“inherit”) features from others independent of the class hierarchy. The difference to
environmental acquisition is that direct acquisition is independent of the containment
hierarchy of an object.
Both forms of acquisition are needed in certain cases. In the context of this thesis, the term
acquisition refers to either direct or environmental acquisition, depending on the context.
5.4.2 Environmental Acquisition
Environmental acquisition is important where the specific environment of objects or components
provides attributes or a context that has, or can have, an impact on these objects and components.
Think of a directory tree of Web pages. The Web pages may inherit attributes from their parent
directories, if these attributes are not found in the same directory. In this case, parent directories
provide the environment for the child directories and contained Web pages. This is the main idea
of Zope, a web server and content management system (see [ACQU98]).
See also Section 6.1 for more details.
5.4.3 Direct Acquisition
In contrast to environmental acquisition, direct acquisition is usually independent of the
containment hierarchy. However, in a containment hierarchy, containers may nevertheless
acquire functionality from their children, or, like with environmental acquisition, children may
acquire from their containers.
96
5 Detailed Concepts and Implementation
5.4.3.1 Acquisition in PCoC
In an acquisition relationship between Activities Providers, respectively their Activity Sets, the
acquiring component has a reference to its acquired components, and vice versa.
<<acquire>>
*
Activities Provider
*
Figure 5.4-1 Acquisition
See also Figure 5.4-3 for an example of an acquisition graph.
Acquisition relationships are managed by Activity Sets and Dispatchers. An Activity Set has a
dictionary of Dispatchers and an ordered list of acquired Activity Sets. A Dispatcher has a
reference to an Activity and/or Dispatchers of acquired Activity Sets. See also Sections 2.3 and
later.
The following picture illustrates two Activity Sets, their Dispatchers, and Activities, for the
following acquisition/discard algorithm.
Activity Set A / Activities Provider A
Activities
<<dispatch>>
MoveFile/A
Dispatchers
MoveFile/A
<<acquire>>,
<<discard>>
Activity Set B / Activities Provider B
Dispatchers
<<acquire>>, <<discard>>, <<dispatch>>
Foo/A
MoveFile/B
Foo/B
Activities
<<dispatch>>
MoveFile/B
Foo/B
...
Figure 5.4-2 Acquire / discard Dispatchers
When an Activity is added or removed, or another Activity Set is acquired, the Dispatcher
dictionary and the list of acquired Activity Sets are brought up to date accordingly:
v When A acquires another Activities Provider B, respectively its Activity Set, add a reference
to B to the list of acquired Activity Sets in A. Add a reference to A to the list of listeners in
B.
v When an Activity MoveFile is added to an Activity Set A, and no Dispatcher
MoveFile/A with this name exists in A, create one with the same name (MoveFile) and
dynamic type (for example, Boolean MoveFile(String fromFile, String
toFile)). Add a reference to this Activity to the Dispatcher. Note that Activity and
Dispatcher names globally identify a dynamic type. In this case, all Dispatchers and
Activities in the system must have the signature Boolean MoveFile(String,
String).
5.4 Acquisition: Dynamic Component Inheritance
97
v If B provides a Dispatcher MoveFile/B with the same name and type as the Dispatcher
MoveFile/A of Activity Set A, add a reference to it to the list of acquired Dispatchers in
Dispatcher MoveFile/A. If no Dispatcher with this name exists, create one. If there is a
type mismatch (for example, if MoveFile/B returns void instead of a Boolean), throw
an error and do not add MoveFile/B.
Note that in this case, Activity MoveFile/A in Dispatcher MoveFile/A actually
overrides MoveFile/B. However, when we invoke Dispatcher MoveFile/A, we can
specify a directive (sendDeep) to send the request through the acquisition branch to a leaf,
in this case Activity MoveFile/B.
Boolean result = (Boolean)perform("|A|MoveFile", args,
PCCDirectives.first().sendDeep());
v If B provides a Dispatcher Foo/B, and A does not provide a Dispatcher with the same name
(and dynamic type), create a new Dispatcher Foo/A in A and add a reference to Foo/B to
the list of acquired Dispatchers in Foo/A. If an Activity is added to B after the acquisition
relationship from A to B has been established, notify A (as well as all other listeners of B) in
order to update the Dispatchers in A.
Discard algorithm:
v When Activity Set A discards B, that is, when it breaks the acquisition relationship, remove
the reference to B from the list of acquired Activity Sets in A. Remove A from the list of
listeners in B.
v Remove all references to Dispatchers of B (MoveFile/B and Foo/B) from the lists of
acquired Dispatchers in the Dispatchers of A (remove MoveFile/B from MoveFile/A
and Foo/B from Foo/A).
v When an Activity Foo/B is removed from an Activity Set, remove it from the
corresponding Dispatcher Foo/B.
v If a Dispatcher Foo/A becomes empty, that is, if it does not contain references to an
Activity or acquired Dispatchers (Foo/B) anymore, remove it from A.
These steps are partly recursive. For example, an Activity Set A might acquire B and B might
acquire C and so forth. When an Activity is added to an Activity Set C, a corresponding
Dispatcher is created in C, then in B, and finally in A.
When an Activity is requested (by invoking a Dispatcher), the request is delegated to that
Activity Set providing the Activity.
See Sections 5.6 and later for a detailed explanation of Dispatchers. See Section 5.8 for a detailed
description of Activity Sets.
5.4.3.2 Sample Acquisition
An acquisition graph as shown in Figure 5.4-3 denotes a delegation relationship. Each Activity
Set holds an ordered list of acquired Activity Sets which defines a priority ranking. If one of the
acquired Activity Sets gains focus, for example through user interaction, the lists of acquiring
Activity Sets are rearranged, such that this Activity Set becomes the first entry, and therefore gets
the highest priority.
Figure 5.4-3 illustrates how Activities Providers acquire others, respectively their Dispatchers,
and how messages are dispatched through the given acquisition branches.
98
5 Detailed Concepts and Implementation
:Application
path = "|Application"
<<acquire>>,
<<is container of>>
<<acquire>>,
<<is container of>>
+child
+child
<<acquire>>,
<<is parent of>>
+parent
+child
ProjectManager: CombinedTool
:FileSystemService
TextEditor: CombinedTool
path = "|Application|ProjectManager[0]"
path = "|Application|FileSystemService"
path = "|Application|TextEditor[0]"
+child
+parent
+parent
+child
<<acquire>>,
<<is container of>>
<<use>>
+parent
+parent
:ProjectBrowserTool
:FileSelectionTool
path = "|Application|ProjectManager[0]|
ProjectBrowserTool[0]"
path = "|Application|ProjectManager[0]|
FileSelectionTool[0]"
+child
<<acquire>>
<<acquire>>,
<<is container of>>
:TextEditorTool
+child
path = "|Application|TextEditor[0]|
TextEditorTool[0]"
Legend:
ProjectBrowserTool has the focus:
TextEditorTool has the focus:
FileSelectionTool has the focus:
Note: A component having the focus
means that its acquisition branch has
the highest priority.
Figure 5.4-3 An Acquisition Graph
The Service Provider Application acquires the Tools TextEditor and
ProjectManager, and a Service Provider FileSystemService. More precisely, its
Activity Set acquires the Dispatchers of their Activity Sets. This is similar to a multiple
inheritance. If the execution of an Activity is requested in Application, then the request is
delegated to the Activity Set providing the Activity. If more than one provides it, the most
recently focussed is used, or in the case of a broadcast, all are used.
The priority used for delegating Activity requests is determined by the order in the list of
acquired Activity Sets. In our example, the order of acquired Activity Sets in Application is
ProjectManager (index 0 in the list), FileSystemService (index 1), and
TextEditor (index 2). However, the node (in the acquisition branch) where a Dispatcher is
invoked in order to delegate an Activity request, has always the highest priority. Note that
indexes in Activity Set names (for example, in TextEditorTool[0]) are automatically
created by the framework in order to get unique names for the Activity Sets of multiple instances
of Activities Providers. They do not reflect the priority ranking in the acquisition graph.
In this case, Application has the highest priority. If itself does not provide a requested
Activity, the request is delegated via the Activities Provider, respectively Activity Set, with the
highest priority in Application, which is in this case ProjectManager. The request is
then delegated further to ProjectBrowserTool.
We say, the branch Application-ProjectManager-ProjectBrowserTool has the
highest priority. That is, the ProjectManager Activity Set is at the first position (index 0) in
the list of acquired Activity Sets in Application, and that of ProjectBrowserTool at
the first position in ProjectManager. Let as assume that the priority changes through user
interaction, more precisely through a mouse-click into TextEditorTool. In this case, the
branch Application-TextEditor-TextEditorTool gets the highest priority.
FileSystemService gets in this case the highest priority relative to TextEditorTool,
but may still have a lower priority relative to Application.
Section 5.6 describes the dispatch mechanism in detail.
5.4 Acquisition: Dynamic Component Inheritance
99
5.4.3.3 Acquiring / Discarding a Component
We assume an acquisition relationship like illustrated in Figure 5.4-3, where Application is
the container of FileSystemService and acquires it.
Program 5.4-1 FileSystemService.java
package example4;
import pcoc.*;
/** A Service Provider for file system operations */
public class FileSystemService extends PCCServiceProvider {
/** The constructor setting the name and id to "MyFileSystemService".
* If the constructor of the base class is not explicitly called with a
* string argument, the class name is used as name.
*/
public FileSystemService() {
super("MyFileSystemService");
activate();
}
/** Set up the Activity Set */
protected void setupActivitySet() {
super.setupActivitySet();
addActivity(new MoveFileActivity());
}
...
// add an operation
/** An Activity for moving a file on the file system.
class MoveFileActivity extends PCCSingleActivity {
MoveFileActivity() {
super("moveFile", "File", "Boolean", "String,String");
}
...
}
...
}
In Program 5.4-1 we see a file-system Service Provider which adds the Activity
MoveFileActivity in setupActivitySet. This Activity gets the name “moveFile”.
It overrides the method doPerform which defines the actual behavior of this Activity, and
doGetState which returns the current state. See a more detailed implementation in Program
4.3-15.
Note that the direct base class of FileSystemService (PCCServiceProvider) or
indirect ones may already have added Activities.
Program 5.4-2 is another implementation of a Service Provider. It serves as root component of a
PCoC application. It instantiates FileSystemService and adds it as element.
Program 5.4-2 Application.java
package example4;
import pcoc.*;
/** A root component in the acquisition graph of an application
*/
public class Application extends PCCServiceProvider {
public Application() {
activate();
PCCActivitiesProvider fsProvider = new FileSystemService();
add(fsProvider);
// calls acquire(fsProvider)
}
...
}
100
5 Detailed Concepts and Implementation
With the current implementation, by default, a container acquires all its elements, and thus acts
as their delegation child. This behavior can be changed individually by either using another call
add(fsProvider, false), or subsequently removing the acquisition relationship to the
child using the method discard. For the example above we would call
discard(fsProvider).
Activities Providers can acquire others independent of their containment relationship by using
acquire(<parent>). See the example below.
Program 5.4-3 AllServices.java
package example4;
import pcoc.*;
import javax.swing.*;
/** A Service Provider combining other Service Providers through acquisition
*/
public class AllServices extends PCCServiceProvider {
/** The constructor acquiring from known Service Providers.
*/
public AllServices() {
activate();
acquire(
// calls getActivitySet().acquire(...)
PCCRegistry.getActivitySet("|Application|MyFileSystemService"));
acquire(
PCCRegistry.getActivitySet("|Application|MyOtherService"));
...
}
...
}
The Activities Provider AllServices, respectively its Activity Set, becomes an acquisition
child of the Activity Sets MyFileSystemService and MyOtherService. We use the
registry to look up the Activity Sets.
In order to break up an acquisition relationship, we can use the method discard. When
discarding an Activities Provider or Activity Set, it is not necessarily destroyed. Other objects
may still have references to fileSystemService and prevent it from being garbage
collected.
Program 5.4-4 Discarding acquisition relationship
PCCActivitySet fileSystemService =
PCCRegistry.getActivitySet("|Application|MyFileSystemService");
PCCActivitySet allServices =
PCCRegistry.getActivitySet("|Application|AllServices");
allServices.discard(fileSystemService); // discard fileSystemService from
// allServices
// semantically equivalent:
allServices.getProvider().discard(fileSystemService.getProvider());
5.4.3.4 Changing the Priorities (the Ranking) of Acquired Activity Sets
The following code fragment illustrates how the priority ranking of an acquisition relationship
can be changed. We use the acquisition graph of Figure 5.4-3.
5.4 Acquisition: Dynamic Component Inheritance
101
Program 5.4-5 Changing the priority ranking in an acquisition graph
PCCActivitySet app = PCCRegistry.getActivitySet("|Application");
PCCActivitySet pm = PCCRegistry.getActivitySet("|Application|ProjectManager[0]");
PCCActivitySet te = PCCRegistry.getActivitySet("|Application|TextEditor[0]");
app.moveFirst(pm); //
//
app.moveFirst(te); //
app.moveLast(te); //
app.moveTo(te, 2); //
ProjectManager[0] has/gets the highest priority;
corresponds to app.moveTo(pm, 0)
change the focus to TextEditor[0]
give TextEditor[0] the lowest priority
move TextEditor[0] to position 2 in the priority ranking
app.perform("copy", PCCDirectives.first());
// directive first is the default
// and can be omitted
app.perform("moveFile");
The most common case is that an Activity Set is moved to the first position in the priority
ranking of an acquiring Activity Set. For example, a frame manager (a layout manager for Tools
in the GUI of an application), may call moveFirst for the Activity Set of a Tool when the user
interactively activates the Tool. A Dispatcher delegates Activity requests to the Activity Set with
the highest priority ranking (the foremost Activity Set in the ordered list of acquired Activity
Sets) which provides the requested Activity. If a text editor Tool has the focus and Dispatcher
directive PCCDirective.first is used, copy is delegated to this Tool. moveFile is
nevertheless delegated to FileSystemService, although it has not the highest priority.
However, since there is no Activity Set with a higher priority that provides a moveFile
Activity, that of FileSystemService is used. Section 5.6.2 describes the dispatch algorithm
in more detail.
5.4.4 Remarks
Both, environmental and direct acquisition support delegation. Objects or components can be
acquired at runtime, either automatically or explicitly. In contrast to environmental acquisition,
direct acquisition allows also discarding of acquisition relationships.
PCoC supports both kinds of acquisition, but uses mainly direct acquisition. Acquisition can be
applied to Activities Providers, or more precisely their Activity Sets only.
5.5 Activities and their Interfaces
We assume that the reader has read the general definitions in Chapter 3, and the basics of
Activities in Section 4.3.5.
This section describes the Activity interface—an interface that is specified and checked at
runtime, and its use with Activities. It is a combination of a parameter and a return value
interface of an Activity. These interfaces specify sets of types and are stored as instances of the
class PCCActivityInterface. This class ensures type safety for arguments and return
values of Activities at runtime. See Section 4.3.4 for a description of the dynamic Activity type,
and Section 4.3.3 for a description of the argument container class of PCoC.
5.5.1 Interface Specification
The interface of an Activity is specified in its constructor.
102
5 Detailed Concepts and Implementation
Program 5.5-1 PCCSingleActivity constructor
public class PCCSingleActivity {
/** Activity constructor. Sets the dynamic type of this operation and
* is called in subclasses.
* @param name The name of this Activity. All Activities with the same
*
name must have the same interface (resultType,
*
argumentType) and category.
* @param category A virtual category to which this Activity should belong.
*
This can be used to update the states of all Activities of a category
*
at once, or to remove them from their Activity Sets.
* @param resultType Return value interface. See PCCActivityInterface.
* @param argumentType Parameter interface. See PCCActivityInterface.
public PCCSingleActivity(String name, String category,
String resultType, String argumentType);
}
Activities must be derived from PCCSingleActivity or PCCMultiActivity. The latter
is not described here, since it does not add any relevant concepts. The only difference to
PCCSingleActivity is that its instances represent sets of Activities which are distinguished
by a separate index parameter. The index parameter is always the first parameter in the Material
passed to a multi-Activity. An Activity class must pass an interface specification to this base
class.
Program 5.5-2 A User Implemented Activity
public class MoveFileActivity : PCCSingleActivity {
/** Activity constructor. Sets the dynamic type of this operation.
* Type declaration: name "moveFile", category "File", returns a Boolean
*
object, needs two strings, the original file path and the new one
MoveFileActivity() {
super("moveFile", "File", "Boolean", "String,String");
}
...
}
In this example an Activity is specified as having a Boolean return value, and two String
objects as arguments. PCoC converts these String specifiers to PCCActivityInterface
objects. These are managed by the framework in a lookup table to share them with Activities
with the same interface.
5.5.2 Retrieving Interfaces
The following methods of PCCAbstractActivity retrieve the return value and parameter
types of a PCCAbstractActivity.
Program 5.5-3 Parameter and Return Value Interface of PCCAbstractActivity
public PCCActivityInterface getArgumentType();
public PCCActivityInterface getResultType();
These interfaces are used by the framework in order to check the types of the arguments passed
to an Activity when it is invoked, and its return value.
5.5.3 Activity Interface Class
The interfaces specified in the constructors of Activities are managed by the class
PCCActivityInterface. The interface declaration strings specified in Activity constructors
are passed to the fromString method of PCCActivityInterface. The most relevant
methods of PCCActivityInterface are listed in Program 5.5-4.
5.5 Activities and their Interfaces
103
Program 5.5-4 PCCActivityInterface.java (excerpt)
/** PCCActivityInterface. This class holds interfaces of Activities and can
* be retrieved by using the Activity methods getResultType and getArgumentType.
class PCCActivityInterface {
/** Create an activity interface.
* The passed string has the following interface:<br><pre>
* Interface = ([InterfaceEntry] {"," InterfaceEntry} ["," "..."]) | "...".
* InterfaceEntry = Classname ["=" DefaultValue] [MayBeNull].
* DefaultValue = "(" Value ")".
* DefaultAccessor = "{" ActivityName "}".
* MayBeNull = "[0]".
* </pre>
* Classname ... a fully qualified classname or java.lang. - Datatype
*
(String, Integer, Boolean, etc.)<br>
* A trailing "..." stands for an arbitrary number of arbitrary arguments<br>
* @param iSpec a string in the given EBNF format
*/
static public PCCActivityInterface fromString(String iSpec) {...}
private PCCActivityInterface(String iSpec) {...}
String asInterfaceString(Class[] typeList) {...}
/** Get the size of this interface.
* @return The number of defined interface entries.
*/
int size() {...}
/** Get the variable argument status.
* @return true, if this interface allows a variable number of arguments
*/
boolean hasVariableArgs() {...}
/** Check if a PCCMaterial matches this Interface.
* @param material the argument list or return value to check
* @return true, if material matches this interface; false, otherwise
*/
boolean isMaterialOK(PCCMaterial material) {...}
}
5.5.4 Type Safety / Implementation of perform
Activity interfaces are used to ensure type safety for arguments and return values of Activities.
Materials passed as arguments and returned must match the Activity interface represented
through PCCActivityInterface objects.
The method isMaterialOK (see Program 5.5-4) is called for each invocation of an Activity. If
the type check for the passed arguments fails, the execution is stopped by throwing an exception
of type PCCPerformException. The same is done if the return value type does not match the
specified interface.
Program 5.5-5 Type Checks in PCCAbstractActivity
public class PCCAbstractActivity {
...
public PCCMaterial perform(PCCSenderInfo si, PCCMaterial arg)
throws PCCPerformException {
if (!canPerform(arg)) {
throw new PCCPerformException("Cannot perform.");
}
checkMaterialType(getArgumentType(), arg, "argument");
notifyListenersBeforePerform(this, arg);
PCCMaterial result = doPerform(si, arg);
checkMaterialType(getResultType(), result, "return value");
notifyListenersAfterPerform(this, arg, result);
return result;
}
}
104
5 Detailed Concepts and Implementation
The template method perform is responsible for checking argument and return value types
using method checkMaterialType. checkMaterialType calls isMaterialOK on
the specified Activity interface if the interface is not null. Before checking the argument types,
the Activity is checked as to whether or not it is in an executable state (a state other than
“Disabled”).
The following code fragment shows some erroneous return statements that lead to exceptions
because of type errors, and a correct statement.
Program 5.5-6 Checking the return value type
public class MoveFileActivity : PCCSingleActivity {
/** Activity constructor. In sets the dynamic type of this operation.
* Type declaration: name "moveFile", category "File", returns a Boolean
object, needs two strings, the original file path and the new one
*
MoveFileActivity() { super("moveFile", "File", "Boolean", "String,String"); }
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args)
throws PCCPerformException {
boolean done = moveFile(
(String)args.getObject(0),
(String)args.getObject(1)));
// return new PCCMaterial();
// exception: returns no value
// return new PCCMAterial(17);
// exception: returns an Integer object
// return new PCCMAterial("foo"); // exception: returns a String object
return new PCCMaterial(done);
// ok:returns a Boolean object; equal to:
//
PCCMaterial retv;
//
retv.add(done);
}
The framework-internal type check reports no errors if a Boolean object is returned, as
expected.
The following code fragment shows some incorrect usage of MoveFileActivity by passing
arguments of wrong types.
Program 5.5-7 Checking the argument types
void testActivity() {
PCCActivity activity = new MoveFileActivity();
PCCMaterial result = null;
PCCMaterial args = new PCCMaterial();
String argumentInterface;
PCCActivityInterface argumentTypes = activity.getArgumentType();
System.out.println(argumentTypes.getInterface());
// prints "String,String"
PCCActivityInterface resultType = activity.getResultType();
System.out.println(resultType.getInterface());
// prints "Boolean"
System.out.println(args.getInterface());
// prints ""
result = activity.perform(null, args);
// exception: passed an empty
//
argument list
args.add("/tmp/fromFile.tmp");
// add a String object
System.out.println(args.getInterface());
// prints "String"
result = activity.perform(null, args);
// exception: passed only one String,
//
two expected
args.add(17);
// add an Integer object (a plain int value is
// automatically converted to an Integer object)
System.out.println(args.getInterface());
// prints "String,Integer"
result = activity.perform(null, args);
// exception: passed a String and an
//
Integer, but two String
//
objects are expected
args.set(1, "/tmp/toFile.txt");
// replace the second argument
// (index 1) with a String
System.out.println(args.getInterface());
// prints "String,String"
result = activity.perform(null, args);
// ok: passed two Strings
}
5.5 Activities and their Interfaces
105
Only the last perform statement works, since only in this case the types of the arguments match
the specification in the constructor. Print statements are used to print current interfaces.
5.6 Dispatchers and Activity Sets: Dispatching Requests
One of the most important concepts of PCoC are Dispatchers. They are used for delegating
requests for the invocation of Activity requests through an acquisition graph. See Section 5.4.3.2
for an acquisition example. We assume, the reader has read Section 4.4.
5.6.1 Activity and Dispatcher Tables
An Activity Provider, respectively its Activity Set has a set of Activities and Dispatchers
managed in dictionaries (key/value-maps).
<<prioritized component coupling>>
Activities Provider
ActivitiesProvider
provides
and performs
Activities
:ActivitiesProvider
<<internal dispatch>>
<<create>>
1
:Activity
1
:ActivitySet
:Activity
1
1
1
1
1
1
:Activity
<<external dispatch>>
:Dispatcher
:Dispatcher
:Dispatcher
:Dispatcher
1
<<create>>
naming service and
context, holding an
ActivitiesProvider's
Activities and Dispatchers;
used for component
coupling and control of
components
:Dispatcher
1
Create or update
Dispatcher when
an Activity is added
to the Activity Set
:Dispatcher
Dispatcher
dictionary, similar
to a method
dictionary
*
*
*
*
*
ActivitiesProvider
<<prioritized dispatching>>
provider
1
:Dispatcher
:Dispatcher
:Dispatcher
:Dispatcher
ActivitiesProvider
:Dispatcher
:Dispatcher
Figure 5.6-1 Component Coupling using Dispatchers
Figure 5.6-1 shows how Activities Providers can be connected via their Dispatchers. Activities
Providers create Activities which are then stored in the Activity dictionary of its Activity Set.
The dictionary is used, for example, for introspection. For each Activity, a Dispatcher is either
created or, in the case that the Dispatcher already exists, updated (see Section 5.4.3). Dispatchers
are stored in the Dispatcher dictionary. The key for looking up a Dispatcher is its (lower-case)
name (or its signature, in an extended version of the framework). Remember, a Dispatcher holds
a list of references to acquired Dispatchers, and an optional reference to an Activity (see Program
2.6-1).
Figure 5.6-2 illustrates the Activity and Dispatcher dictionaries of a specific Activities Provider.
The tables are managed by its Activity Set. See Section 5.4.3 for a description of how Activities
are added to an Activity Set, and the algorithm for creating corresponding Dispatchers.
When a Dispatcher is invoked, it uses its own Activity reference and/or gathers all Activities
from directly and indirectly acquired Dispatchers in a distinct and ordered list and delegates the
request to one or many Activities of the resulting set (depending on the specified directives).
Note that we do not directly store acquired Activities in Dispatchers, but only references to their
associated Dispatchers, in order to save memory and to keep updates simpler.
In the given example, foo is acquired from another Activity Set, and thus requests for
performing it are in each case delegated to this Activity Set, whereas copy requests may be
performed locally (for example, when using directive PCCDirectives.first()).
106
5 Detailed Concepts and Implementation
Activities Provider
provides
and performs
Activities
1
provider
1
<<internal dispatch>>
1
<<create>>
<<external dispatch>>
1
copy: Dispatcher
*
getSelection: Dispatcher
*
copy: <<Activity>>
1
1
1
getSelection: <<Activity>>
1
1
:ActivitySet
1
displaySelection: <<Activity>>
1
<<create>>
naming service and
context, holding an
ActivitiesProvider's
Activities and Dispatchers;
used for component
coupling and control of
components
*
displaySelection: Dispatcher
*
foo: Dispatcher
Create or update
Dispatcher when
an Activity is added
to the Activity Set
dispatchers of other
Activities Providers
:ActivitiesProvider
Dispatcher
dictionary, similar
to a method
dictionary
Figure 5.6-2 Activity and Dispatcher Tables of a Component
5.6.2 Dispatching Activity Requests
Figure 5.6-3 depicts the coupling of four Activities Providers by their Dispatchers.
Application acquires AP1 and AP2. AP1 acquires AP11.
AP1: <<Activities Provider>>
Application: <<Activities Provider>>
clear
clear
copy
copy
displaySelection
<<dispatch>>
clear
copy
displaySelection
Dispatcher
table, similar
to a method
dictionary
Activities
AP11: <<Activities Provider>>
cut
paste
displaySelection
displaySelection
getSelection
clear
cut
paste
Dispatcher
table, similar
to a method
dictionary
Activities
getSelection
AP2: <<Activities Provider>>
Dispatcher
table, similar
to a method
dictionary
copy
copy
Dispatcher
table, similar
to a method
dictionary
Activity
table
(internal
operations)
Activities
Figure 5.6-3 Dispatching Activity Requests
Some of the components in Figure 5.6-3 provide a copy Activity, therefore each of them has
automatically created a Dispatcher for copy.
5.6 Dispatchers and Activity Sets: Dispatching Requests
107
A request for performing copy in Application is sent via its Dispatcher to the Activities
Provider, respectively Activity Set, that most recently had focus and that provides a copy
Activity.
Program 5.6-1 Invoking a Dispatcher
Application app = new Application();
app.invoke("copy", null, PCCDirectives.first());
app.getActivitySet().invoke("copy", null, PCCDirectives.first());
This code fragment shows the invocation of the copy Dispatcher. The last two statements are
semantically equivalent.
In this case, the request might be sent to AP1, i.e., the Dispatcher algorithm follows its first
branch to AP1, gets the copy Activity there, and invokes its perform method in a second step.
We say that a perform request is sent to the copy Activity of AP1 in this case. On the other
hand, getSelection would be performed directly in Application.
A request for performing displaySelection would always be sent to AP11 over AP1, since
AP11 is the only component that provides this Activity.
Program 5.6-2 Dispatch algorithm
final public class PCCDispatcher extends PCCAbstractActivity
implements PCCActivityListener {
/** finds activities starting the search at the current dispatcher and
* using a given strategy. Found activities are stored in the strategy object.
* This framework-internal method is mainly used for broadcasts.
* @param strat the strategy used for searching activities
*/
void findMatchingActivities(ActStrategy strat) {
PCCDirectives directives = strat.getDirectives();
if (fActivity != null) {
strat.addActivity(fActivity);
if (!directives.sendsDeep()) { return; }
}
PCCDispatcherImpl activity = null;
if (directives.isBroadcast()) {
int sz = fDispatchers.size();
int i = 0;
int endv = sz;
int offset = 1;
if (directives.sendsBackward()) {
i = sz-1;
endv = 0;
offset = -1;
}
while (i != endv) {
activity = (PCCDispatcherImpl) fDispatchers.get(i);
if (activity != null &&
(!directives.sendsToActiveOnly() ||
activity.getActivitySet().isActive())) {
activity.findMatchingActivities(strat);
}
if (i == endv) { // i is an int, therefore we must check the value
break;
// before getting an underrun with i-}
i += offset;
}
}
}
...
}
This code fragment shows the algorithm for gathering Activities through all acquisition branches
of a Dispatcher.
108
5 Detailed Concepts and Implementation
Basically, the dispatch algorithm of PCoC consists of two phases: the search and the execution.
v Follow one or many acquisition branches of the given Dispatcher in the given order
(priority) and collect all Activities belonging to the given Dispatcher in a distinct and
ordered list. This can be done independently of Activity Sets, since Dispatchers store the
references to Activities and acquired Dispatchers with the same name. The resulting set has
no duplicate Activities. In the case of a single request, for example using specifier first,
only the branch with the highest priority is visited. In the case of broadcasts all acquisition
branches are visited in the given order.
v Invoke a method on each Activity in the retrieved set, e.g. getState, perform, etc.
The directives are used to filter the search while iterating over the list of acquired Dispatchers
(fDispatchers). If an Activity (fActivity) is found, it is added to the resulting strategy
object.
Program 5.6-3 is an excerpt of the base class for strategy objects passed to the dispatch algorithm
(cf. Program 5.6-2).
Program 5.6-3 Dispatch-strategy class (1)
abstract class ActStrategy implements PCCActivityListener {
ActStrategy(boolean robust, PCCMaterial arg, PCCDirectives directives,
String filter, ArrayList activities) {
...
}
/** performs this strategy on each activity found by
* <code>findMatchingActivities</code> using this strategy
*/
Object perform() {
int i = 0;
while (i < fActivities.size()) {
PCCActivity activity = (PCCActivity) fActivities.get(i);
if (activity != null) { doPerform(activity); }
i++;
}
performed();
return fResult;
}
}
...
The method perform is called by client code and represents the actual strategy. Subclasses
must override the method doPerform.
Program 5.6-4 Dispatch-strategy class (2)
class PerformStrategy extends ActStrategy {
PerformStrategy(PCCMaterial arg, PCCDirectives directives, String filter) {
super(true, arg, directives, filter, null);
}
/** invokes a method corresponding to this strategy class on a single activity.
* @param activity the activity to be performed
*/
protected void doPerform(PCCSenderInfo si, PCCActivity activity) {
try { fResult = activity.perform(si, (PCCMaterial) fArg); }
catch (PCCPerformException pe) { fResult = pe; }
}
}
In this case, the perform method of every single Activity is invoked. Beside
PerformStrategy, there are several other strategy classes, one for each Activity request that
a Dispatcher can delegate: getState, countActivities, getContextData (see
Section 4.4), etc.
5.6 Dispatchers and Activity Sets: Dispatching Requests
109
Program 5.6-5 Using a dispatch-strategy class
final public class PCCDispatcher extends PCCAbstractActivity
implements PCCActivityListener {
public void perform() {
PerformStrategy strat = new PerformStrategy(arg, directives, filter);
findMatchingActivities(strat);
Object result = strat.perform();
}
}
Having different strategy classes, the Dispatcher can reuse the same algorithm for different
things.
Using strategies instead of duplicating an algorithm is a good way to reduce the danger of
making mistakes during maintenance or extension of the algorithm. It also reduces the
maintenance effort, since the really difficult code is just in one place and has only to be
maintained there.
5.7 Tasks in Detail
We assume, the reader has read Section 4.5 as introduction to the concept of Tasks. Now we go
more into detail.
5.7.1 Structure of Tasks
Tasks are usually defined via configuration. The following excerpt of a configuration file shows
a definition of a Task (basically equivalent to Program 4.5-1).
Program 5.7-1 Defining a Task via configuration
<RCTask>
<name>displaySelectionTask</name>
<dispatcher directives="First">|Application|displaySelection</dispatcher>
<args>
<dispatcher>|Application|getSelection</dispatcher>
<integer>2</integer>
</args>
<preconditions>
<dispatcher>|Application|foo</dispatcher>
<task>myCondition</task>
</preconditions>
...
</RCTask>
See Section 4.5 for a description of this sample configuration.
Program 5.7-2 Using a Task
public class MyActivitiesProvider extends PCCServiceProvider {
...
public void foo() {
// Get or create a Task; calls getActivitySet().getOrCreateTask(...);
PCCTask task = getOrCreateTask("displaySelectionTask");
if (task.canPerform()) { // if state != disabledState(), and all
task.perform();
// Dispatchers are available and executable, and
}
// all arguments are provided via configuration,
// and the types of the passed arguments match
}
// the dynamic type of the Dispatcher
}
In this example, we use the method getOrCreateTask,
PCCActivitiesProvider and PCCActivitySet, to create a task.
provided
by
110
5 Detailed Concepts and Implementation
PCoC creates an instance of the PCCTask class and initializes the object with the given
RCTask definition.
Program 5.7-3 Using a Task
public final class PCCActivitySet implements PCCActivitySetListener {
...
/** Create new or get existing Task associated with the given name. If no
* Task instance with this name exists in this Activity Set, it is created
* and initialized with the corresponding RCTask definition from the
* configuration. References to already created Tasks are stored in a
* hash map (fTaskMap) in this Activity Set.
* @param name The name of the item used to get or create a task.
*/
public PCCTask getOrCreateTask(String name) {
PCCTask task = (PCCTask) fTaskMap.get(name.toLowerCase());
if(task == null) {
task = createAndRegisterTask(ResConfigMap.getTask(name));
}
return task;
}
public PCCTask createAndRegisterTask(RCTask item) {
if (item == null) { return null; }
PCCTask task = new PCCTask(this, item);
if(task.isUnusable()) { // check if the arguments match the dynamic type
task.cleanup();
task = null;
}
else { fTaskMap.put(item.getName().toLowerCase(), task); }
return task;
}
}
Note that ResConfigMap is a singleton that holds a representation of the whole XMLconfiguration of a PCoC application. All definitions are available as objects in ResConfigMap
and can be looked up using their name. The name is the value enclosed in the corresponding
name-tag
of
the
XML-configuration.
See
Program
5.7-1
(<name>displaySelectionTask</name>).
Program 5.7-4 PCCTask constructor
/** create a PCCTask from RCTask
* @param as the Activity Set the Task belongs to
* @param rcTask defines the Task (name, associated dispatcher, arguments, etc.)
*/
PCCTask(PCCActivitySet as, RCTask rcTask) {
String name = rcTask.getName();
String associatedDispatcher = null;
String directives = null;
RCDispatcherReference dispRef = rcTask.getDispatcher();
if(dispRef != null) {
associatedDispatcher = dispRef.getContent(); // set dispatcher, can be null
// for macros
directives = dispRef.getDirectives();
// set dispatcher directives
}
PCCMaterial args = null;
RCArray rcArgs = rcTask.getArgs();
args = convertRCArrayToMaterial(rcArgs);
// set arguments
setupConditions(rcTask);
// set up preconditions
// for performing this Task
// initialize member variables, and macro (if this Task represents a macro)
init(as, name, associatedDispatcher, args, directives, rcTask);
...
}
The implementation of the PCCTask constructor is shown above. This code fragment should
help to get a rough overview of how the configuration and instances of PCCTask are related to
5.7 Tasks in Detail
111
each other. We do not show the whole implementation of the PCCTask class, since it is too
huge to be described here, and it does not add relevant information to get more insight into the
concepts of the framework.
The member variables (fActivitySet, fName, fArguments, etc.) of the Task are set in
init. The values are taken from the configuration passed to the constructor.
Arguments are converted in a method convertRCArrayToMaterial from a string
representation to corresponding primitive data-type objects. For example, a value following an
<integer> tag in the configuration (Program 5.7-1) is converted to an Integer object, etc.
The method also checks whether the argument types match the dynamic type of the main
Dispatcher of the Task (if there is any). If an argument is a reference to a Dispatcher or Task, the
referenced Dispatcher or Task is performed and temporarily replaced by its return value when the
current Task is performed.
For each precondition (if there is any), a corresponding reference to a Task object or Dispatcher
is stored in an ArrayList (fPreConditions) using the method setupConditions.
When the Task is performed, for each Task and Dispatcher in this list the method canPerform
is called in order to check whether the Task can be performed.
5.7.2 Dispatching Requests using Tasks
Figure 5.7-1 illustrates how Dispatchers of a Task delegate Activity requests to different acquired
Activities Providers. Note that the prefix PCC of class names are omitted in this diagram
(PCCDispatcher, PCCActivity, etc.)
We assume that the Activity Set of an Activities Provider Application acquires three others,
AP1, AP2, and AP3.
The Task displaySelectionTask is defined in the context of the Application
Activities Provider. The two Dispatchers associated with this Task, displaySelection and
getSelection, delegate requests for retrieving states and performing Activities to the
acquired Activity Sets.
Requests are always delegated to the Activities Provider, respectively Activity Set with the
highest priority capable of handling the request. In the case of a broadcast (specified as
Dispatcher directive), all capable Activities Providers are addressed in the order of their priority
ranking.
Let us assume that we have the following priority ranking in the list of acquired Activity Sets in
Application: AP2, AP3, AP1.
In this case, displaySelection requests are delegated to AP2 while getSelection
requests are delegated to AP3. Note that the priority ranking can change at any time.
The return value of getSelection is used as argument for the invocation of the
displaySelection Dispatcher. We say, getSelection is a data source, and
displaySelection the data sink for displaySelectionTask.
112
5 Detailed Concepts and Implementation
Application: ServiceProvider
path = "|Application"
fAcquiredActivitySets: ArrayList = {AP2, AP3, AP1}
displaySelectionTask: Task
fDispatcher = :Dispatcher
interface= void displaySelection(Selection,Integer)
fArguments= :Material
AP1: ServiceProvider
:Material
:Integer
fArgs = {:Selection, :Integer}
value = 2
path = "|Application|AP1"
<<data sink>>
displaySelection: Dispatcher
displaySelection: Activity
getSelection: Dispatcher
getSelection: Activity
displaySelection: Dispatcher
path = "|Application|displaySelection"
interface = void displaySelection(Selection,Integer)
fDispatchers = {...}
AP2: SimpleTool
path = "|Application|AP2"
getSelection: Dispatcher
displaySelection: Dispatcher
displaySelection: Activity
path = "|Application|getSelection"
interface = Selection getSelection()
fDispatchers = {...}
getFoo: Dispatcher
getFoo: Activity
<<data source>>
getFoo: Dispatcher
AP3: SimpleTool
path = "|Application|getFoo"
interface = Selection getSelection()
fDispatchers = {...}
path = "|Application|AP3"
getSelection: Dispatcher
getSelection: Activity
Figure 5.7-1 A Task Coupling Components With Dispatchers
5.8 Activity Sets: Putting Everything Together
We have seen how Activities Providers expose Activities, how Activities are invoked via
requests from Dispatchers, and how Activities Providers collaborate using these elements. We
have also seen how Activities Providers are organized in dynamic containment hierarchies and
acquisition relationships.
Activity Sets are responsible for keeping all dynamic elements and structural information
together. They are created automatically and invisibly to component developers by Activities
Providers when they are initialized. They can also be created independently of Activities
Providers, simply providing sets of operations.
Please read Section 3.3 for a short introduction into Activity Sets.
5.8.1 Architecture Overview
Figure 5.8-1 illustrates how Activity Set, Activities Provider, Activity, and Dispatcher classes are
related to each other, and roughly explains the meaning of the classes.
See Sections 5.3 and 5.4.3 for the most relevant facilities supported by Activity Set: containment
hierarchy and prioritized acquisition.
Figure 5.8-1 Activity Set Architecture
MultiActivity
*
prioritized
delegation
(distribution)
For example, use a Task, which is an autonomous unit consisting of a
combination of Dispatchers and arguments. Can be used for invocation of
single Activities and as scripts: performTask("Copy") , where Copy
would invoke a Dispatcher |Application|Copy , which belongs to
Activity Set Application and Dispatches the request to a concrete
Activity—for example Activity Copy of Activity Set |Application|Editor .
Such Activity invocation requests are always forwarded by one or more
Dispatchers to a concrete Activity anywhere in the system, depending on the
Activity Set priorities and forwarding directives.
When an Activity is performed, it usually calls, possibly virtual, methods of
its Activities Provider. It can also request the invocation of other Activities
in the system via invocation of Dispatchers or Tasks.
If using a Dispatcher, directly call:
perform("|Application|Copy") or
perform("|Application|Editor|Copy") .
*
*
task
container
1
*
1
0..1
1
*
*
*
0..1
Create or update
Dispatcher when
an Activity is
added to the
Activity Set
1
activitySet
1
Activities Provider
1 configuration
1
1
provider
<<instantiation>>
activitySet
1
1
container
*
*
*
acquiredSets
{ordered} <<acquire>>
a unit that
can be deployed
independently of its
environment. Contains
and manages necessary
properties and resources.
Activity Set
1
persistent storage
(for toolbars, menus,
etc.) or temporary
(for user code)
<<instantiation>>
<<configuration>>
(resources, properties)
for each
local or acquired
Activity
<<instantiation>>
delegates
Activity invocation
requests,
using directives
configuration
<<component configuration>>
(resources, properties)
Activity
provider
element
1..*
*
Task
1
Dispatcher
*
<<instantiation>>
1
*
macro <<script>>
an operation implemented
as class. Has a state which can
be tested before execution,
e.g. Enabled or Disabled
AbstractActivity
an Activities Provider creates Activities as inner or anonymous classes. It can
request the invocation of an Activity by invoking a Task or Dispatcher.
SingleActivity
Activity
for
general
use.
represents an arbitrary
number of Activities of the
same dynamic type.
The actual number is
determined and can be changed
at runtime. Used for
dynamic utilities like history
menus, where the number and
contents of entries are dynamic.
MappedActivity
a proxy for and
adapter to a Task
with a potentially
differing interface.
Created
automatically using
definitions in the
configuration.
synchronous or
asynchronous single or
composite (script) Activity
with preconditions.
Used within sourcecode or
generically via configuration
for toolbars and menus
the name service
used for loosely coupling
Activities Providers, providing
hierarchical structuring,
acquisition, and priority
management at runtime.
adds the acquired Activity Set
to an ordered list and its
Dispatchers to Dispatchers of
the acquiring Activity Set.
The ordered list serves for
prioritized delegation of Activity
requests by Dispatchers.
If an Activity Set is focussed, it
is moved to first place in the
ordered list of the acquiring
Activity Set, thus giving it,
respectively its Activities Provider,
the highest priority.
Activities Set
5.8 Activity Sets: Putting Everything Together
113
114
5 Detailed Concepts and Implementation
5.8.2 Dispatching Activity Requests in Activity Sets
Program 5.8-1 Activities Provider methods
public class PCCActivitiesProvider {
/** Lets a Dispatcher perform an Activity using forwarding
* directives.
* @param dispatcher path and name of an activity Dispatcher,
*
e.g. "|Application|Copy"
* @param arg The argument for the Dispatcher to perform.
* @param directives the forwarding directives for the Dispatcher,
*
e.g. PCCDirectives.defaultDirectives()
* @return The result of the performed activity
*/
public PCCMaterial perform(String dispatcher, PCCMaterial arg,
PCCDirectives directives)
throws PCCPerformException {
if (!isInitialized()) { return null; }
return getActivitySet().perform(dispatcher, arg, directives);
}
}
PCCActivitiesProvider provides a perform method which is used to send a request
for the execution of an Activity through the acquisition graph, starting at a specific Dispatcher.
The method calls the corresponding method in its Activity Set if it is created and initialized.
Program 5.8-2 Activity Set methods
public final class PCCActivitySet implements PCCActivitySetListener {
/** Lets a Dispatcher invoke (forward) an Activity using forwarding directives.
* @param dispatcher path and name of a Dispatcher, e.g. "|Application|Copy"
* @param arg The argument for the Dispatcher to invoke.
* @param directives the forwarding directives for the Dispatcher,
*
e.g. PCCDirectives.defaultDirectives()
* @return The result of the performed Activity
*/
public PCCMaterial perform(String dispatcher, PCCMaterial arg,
PCCDirectives directives)
throws PCCPerformException {
PCCDispatcherImpl dispatcherImpl = getDispatcherImpl(dispatcher);
if (dispatcherImpl != null) {
return dispatcherImpl.perform(new PCCSenderInfo(this), arg,
directives, null);
}
else {
throw new PCCPerformException("No Dispatcher '" + dispatcher + "'");
}
}
...
}
The user passes a path that describes a Dispatcher. This includes the path of the Activities
Provider, respectively its Activity Set, where the Dispatcher should be retrieved, and its name.
For example, “|Application|copy” means Dispatcher “copy” in the Activities Provider
with path “|Application|”.
Note, if no “|” is specified as first character, the path is considered relative to the path of the
current Activity Set. For example, calling perform with path “TextEditor[0]|copy” in
an Activity Set “|Application” is resolved to “|Application|TextEditor|copy”.
5.8.3 Dispatcher Proxy Management
In Program 5.8-2 a DispatcherImpl instance is used to forward or delegate requests for
executing Activities. A DispatcherImpl instance provides the behavior of a Dispatcher,
whereas instances of the class Dispatcher are proxies to DispatcherImpl instances.
5.8 Activity Sets: Putting Everything Together
115
Beside using the perform method of an Activities Provider or Activity Set
Program 5.8-3 Invoking an Activity
perform("|Application|copy", null, PCCDirectives.first());
we can use Dispatcher proxies to invoke Activities
Program 5.8-4 Invoking an Activity using a Dispatcher proxy
Dispatcher dispatcher = getDispatcher("|Application|copy");
dispatcher.perform(null, PCCDirectives.first());
Program 5.8-3 and Program 5.8-4 are equivalent. The Dispatcher proxy (Program 5.8-4) can be
stored for later use regardless of whether the underlying DispatcherImpl instance exists or
not. So we can attach listeners to the Dispatcher proxy without caring about the lifetime of the
corresponding DispatcherImpl instance. A Dispatcher proxy sends notifications whenever
its DispatcherImpl instance is created or destroyed, and it forwards all notifications that its
DispatcherImpl provides, e.g., state changes of Activities.
Program 5.8-5 Dispatcher lookup (class PCCActivitySet)
/** Gets a Dispatcher.
* @param dispatcher the name or path of the requested Dispatcher
* @return the Dispatcher; null if not found
*/
public PCCDispatcher getDispatcher(String path) {
int lastsep = path.lastIndexOf(fcPathSeparatorChar);
if (lastsep >= 0) {
PCCActivitySet as = null;
String asName = path.substring(0, lastsep);
as = getOrCreateChild(asName); // get Activity Set associated with given path
if (as != null) { return as.getDispatcher(path.substring(lastsep+1)); }
return null;
}
String lowername = path.toLowerCase();
DispatcherTag dispatcherTag = (DispatcherTag) fDispatcherMap.get(lowername);
if (dispatcherTag == null) {
dispatcherTag = new DispatcherTag();
fDispatcherMap.put(lowername, dispatcherTag); // create new Dispatcher entry
}
PCCDispatcher dispatcher = null;
if (dispatcherTag.fDispatcher != null) {
// get the corresponding Dispatcher
dispatcher = (PCCDispatcher)dispatcherTag.fDispatcher.get();
}
// (proxy), if any
if (dispatcher == null) {
// create new one if necessary
dispatcher = new PCCDispatcher(this, path);
dispatcherTag.fDispatcher = new WeakReference(dispatcher);
if (!dispatcherTag.fRemoved) {
// add a reference to the
// DispatcherImpl, if not
dispatcher.activityAdded(dispatcherTag.fDispatcherImpl);
}
// removed since the last change
}
// propagation of this Activity Set
return dispatcher;
}
For example, for some reason, we might want to use and store a Dispatcher
“|Application|FileSystemService|getDirectories” in one of our classes,
whilst the corresponding Activities Provider (FileSystemService) and Activity
(getDirectories) have not been loaded, yet. When the Activities Provider and its Activity
are instantiated, a DispatcherImpl object is created and associated with the Activity and a
possibly existing Dispatcher. The Dispatcher is then notified by its DispatcherImpl. Finally,
the Dispatcher notifies its listeners. Let us assume that our object holding a reference to the
116
5 Detailed Concepts and Implementation
Dispatcher is a listener of it. The object might instantly invoke the Dispatcher after the Activity is
created (for example, in order to display the resulting directories in a list view, or to do some
initialization or synchronization of data between various Activities Providers and the newly
loaded one, etc.).
Dispatcher instances are stored in the same table as DispatcherImpl instances. They are
stored and looked up using their lower case names (see Program 5.8-5).
If no Dispatcher with the given name has ever been looked up before, an instance is created. It is
stored in a WeakReference, therefore it will be cleaned up by the garbage collector as soon as
it is not used by client code anymore. We assume that the reader is familiar with the
WeakReference class of Java.
If there is a corresponding DispatcherImpl for the requested Dispatcher instance, the
framework adds a reference to it using activityAdded. If there is no DispatcherImpl
instance, then the reference is set whenever an instance is created and added to the Activity Set.
5.9 Implementation Issues
5.9.1 Java or C++?
As mentioned earlier in this thesis, the PCoC framework was implemented in C++ and later
ported to Java. The reason for the migration to Java was that Java provides a platform that is
available on many operating systems. This includes a sophisticated standard set of class libraries.
Usually, class libraries either have to be bought and integrated with others in an application, or
implemented from scratch. There are also open-source class libraries, which can be adapted,
integrated, and used.
However, the selection, integration, implementation, and maintenance of class libraries are
expensive and ongoing tasks. Particularly, the training of new developers is expensive and timeconsuming. Hence we decided to move to Java, which provides a commonly known and accepted
set of libraries, respectively packages.
5.9.2 Supporting Implicit Generation of Activities
Activities can be generated implicitly from method definitions. We had not to implement
Activities, since, for example, all public method would be automatically exposed as Activities.
The implementation of the generic Activity class could look like Program 5.9-1.
The constructor takes a method object as argument and initializes the Activity with the method
name and its result- and parameter-types.
asInterfaceString generates interface specification strings for the parameter type list and
the return value type of the given method. The implementation of this method is omitted, since it
does not add relevant information to this example. See Sections 4.3.4 and 5.5.1 for descriptions
of the dynamic Activity type concept and the PCCActivityInterface class.
When the Activity is invoked, its arguments are unpacked and passed to the method. The method
is invoked by using dynamic method invocation (perform).
5.9 Implementation Issues
117
Program 5.9-1 A generic Activity
class GenericActivity extends PCCSingleActivity {
Method performMethod;
Method stateMethod;
GenericActivity(Method m, Method stateMethod) {
super(m.getName(), "Generated",
PCCActivityInterface.asInterfaceString(m.getReturnType()),
PCCActivityInterface.asInterfaceString(m.getParameterTypes()));
this.performMethod = m;
if (stateMethod != null) { // check method interface
Class[] parameterTypes = stateMethod.getParameterTypes();
Assert.assert("Wrong state method interface. No parameters expected.",
(parameterTypes == null || parameterTypes.length == 0));
Class returnType = stateMethod.getReturnType();
Assert.assert("Wrong state method interface. Must return a string.",
(returnType != null && returnType == String.class));
}
}
this.stateMethod = stateMethod;
}
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args)
throws PCCPerformException {
Object result = null;
Class cl = fProvider.getClass(); // provider is set in addActivity
Object[] arguments = args.toArray();
try { result = performMethod.invoke(fProvider, arguments); }
catch ...
// NoSuchMethodException, IllegalAccessException,
// InvocationTargetException
return new PCCMaterial(result);
}
protected String doGetState() {
if (stateMethod != null) {
String result = "";
Class cl = fProvider.getClass()
try { result = (String)stateMethod.invoke(fProvider, new Object[] {}); }
catch ...
// catch exceptions as above
return result;
}
return defaultState(true); // is always "Enabled" in this case
}
...
}
The framework retrieves methods by using getMethods of the Java reflection package:
Program 5.9-2 Automatically generating Activities
public class PCCActivitiesProvider {
...
bool addMethodsAsActivities() {
// create Activities from public methods
Method[] methods = getClass().getMethods();
int i=0;
while (i < methods.length) {
PCCActivity activity = new GenericActivity(methods[i], null);
addActivity(activity); // calls also activity.setProvider(this);
i++;
// search the first method with the given name
}
}
}
The framework iterates over all public methods of the Activities Provider, and adds them as
Activities by using addActivity.
This approach looks comfortable for developers, since it is not necessary to implement,
instantiate, and add Activities explicitly. However, we threw away this approach. There are
several reasons for not generating Activities automatically:
118
5 Detailed Concepts and Implementation
v We need a rule to determine which methods should be exposed as Activities and which not,
in order to limit the number of allocated objects. We usually only want methods to be
exposed, which are reasonable for component collaboration and control.
One solution is to let the framework only generate Activities from public methods with a
certain name prefix (programming by convention), but this might not be appropriate for all
use cases. See Program 5.9-3.
v It might cause security problems if all methods were available as Activities, since they
would be automatically accessible via all application interfaces. All Activities would be
exposed, for example, via XMLRPC. This might include also file system operations. Java
provides a security mechanism, but if a method is forwarded via JNI to native code in C++,
then there is definitely a security problem.
v If we do not create Activities for each method, then heuristics or other, maybe semiautomatic mechanisms must be used to select the methods for which Activities have to be
created. This makes it almost impossible for the component developer to get and retain an
overview of which Activities will be available at runtime.
v “There is a lot of magic inside”. Automatic mechanisms can make the implementation and
maintenance of components more difficult. Even without automatic mechanisms, a dynamic
approach such as PCoC may make the development of client code more difficult without
proper utilities. For example, searching for errors in the behavior of a component is almost
impossible, if we do not have an overview of which Activities and Activity Sets are created
at runtime. However, there are two approaches that can help us with this problem:
w The framework provides an introspection mechanism that allows the browsing of
Activity Set hierarchies and acquisition relationships, including Activities, Dispatchers,
Tasks, etc., per Activity Set. This helps us to get an overview of available Activities at
runtime.
w If we implement each Activity in a separate class, we can browse all Activities at
development time with source code and class browsers. The implementation effort is
slightly higher in this case.
v We could use JavaBeans, but they do not have a built in support for state handling.
Activities have a state which can be set at runtime. Examples are “Enabled”, “Disabled”,
“Active”, etc. This does not mean that JavaBeans cannot be used together with Activities;
on the contrary, an Activity can be used to control a JavaBean or vice versa.
The advantage of supporting implicit generation of Activities is the reduced implementation
effort.
Program 5.9-3 Automatically generating Activities, using a naming convention
public class PCCActivitiesProvider {
...
bool addMethodsAsActivities() {
// create Activities from public methods
Method[] methods = getClass().getMethods();
int i=0;
while (i < methods.length) {
if (methods[i].getName().substring(0,3).equals("pcc")) {
PCCActivity activity = new GenericActivity(methods[i], null);
addActivity(activity); // calls also activity.setProvider(this);
}
i++;
// search the first method with the given name
}
}
}
5.9 Implementation Issues
119
As mentioned, a better approach is to limit the implicit generation of Activities through a naming
convention (using a name prefix), but it nevertheless is not satisfying. For completeness, the
corresponding implementation is illustrated above.
The framework iterates over all public methods with prefix “pcc” of the Activities Provider, and
adds them as Activities by using addActivity.
5.9.3 Explicit and Semi-automatic Generation of Activities
After many discussions with framework users we came to the conclusion that, although the
implicit generation of Activities is comfortable, the cross-relations in the source code are easier
to understand if the creation of Activities is driven by the developer.
The following aspects must be considered:
v Explicit mechanisms are more accepted by PCoC users than implicit ones. Implicit features
are fine as long as they do exactly what is desired. As soon as there is unexpected or
unwanted behavior, it is a huge effort to find out how to change it. For example, it is
difficult to find out at design time which Activities or better Activity instances will be
generated at runtime. Other examples are special features (such as personalized menus)
newly introduced to an operating system. For a user, it is difficult to switch a feature off, if
he does not know yet, how the feature is actually called and where he can find proper
information (without reading a huge user manual).
v Current PCoC users are content with the explicit definition of Activities. There is little
reason why we should change a successful system.
The solution we chose was to support explicit definition of Activities (see Program 5.9-4) and
user-driven generation of Activities (see Program 5.9-5 and Program 5.9-6).
The following code fragment shows the explicit definition of an Activity:
Program 5.9-4 An Activity (explicit definition)
public class FileSystemProvider extends PCCServiceProvider {
...
public void foo() {
addActivity(new MoveFileActivity());
}
class MoveFileActivity extends PCCSingleActivity {
MoveFileActivity() {
super("moveFile", "File", "Boolean", "String,String");
}
protected PCCMaterial doPerform(PCCSenderInfo si, PCCMaterial args)
throws PCCPerformException {
boolean done = moveFile(
(String)args.getObject(0),
(String)args.getObject(1)));
return new PCCMaterial(done);
}
protected String doGetState() {
return defaultState(true); // is always "Enabled" in this case
}
}
}
120
5 Detailed Concepts and Implementation
Activities can be generated from a method using reflection:
Program 5.9-5 Semi-automatic generation of Activities
public class FileSystemProvider extends PCCServiceProvider {
...
public void foo() {
addActivity("moveFile");
}
public boolean moveFile(String, String) { ... } // some code
}
The method will be called in the doPerform method of the Activity.
If state handling is required for a generated Activity, a separate method can be specified:
Program 5.9-6 An Activity(with an additional state-retrieval method)
public class FileSystemProvider extends PCCServiceProvider {
...
public void foo() {
addActivity("moveFile", "getMoveFileState");
}
public boolean moveFile(String, String) { ... } // some code
public String getMoveFileState() {
return enabledState(); // equivalent to "Enabled"
}
}
The additional method will be invoked in the doGetState method of the Activity.
The advantage of this approach is that a component developer can explicitly specify the methods
to be exposed as Activities. For each Activity, no matter if implicitly generated or explicitly
defined, a Dispatcher is created.
addActivity is a convenience method of PCCActivitiesProvider. It generates an
Activity from a specified method, adds it to the Activities Provider’s Activity Set, and does some
additional checks. See the implementation below:
Program 5.9-7 Explicitly generating Activities
public class PCCActivitiesProvider {
...
bool addActivity(String performMethodName, String stateMethodName) {
// create Activities from public methods
Method[] methods = getClass().getMethods();
int i = 0;
Method performMethod = null;
Method stateMethod = null;
while (i < methods.length) {
String methodName = methods[i].getName();
if (methodName.equals(performMethodName)) {
performMethod = methods[i];
}
else if (methodName.equals(stateMethodName)) {
stateMethod = methods[i];
}
if (performMethod != null && stateMethod != null) {
break;
}
i++;
// search the first method with the given name
}
PCCActivity activity = new GeneratedActivity(performMethod, stateMethod);
addActivity(activity); // calls also activity.setProvider(this);
}
}
5.9 Implementation Issues
121
The implementation of class GeneratedActivity can be found in Program 5.9-1.
For most cases the framework-supported generation of Activities (Program 5.9-5, Program 5.9-6,
and Program 5.9-7) will do. However, in some cases one may want to do some special things
where information is needed about the Activity (its environment, etc.), or one wants to group or
encapsulate functionality in one Activity. In such a case, it makes sense to explicitly define
Activities as in Program 5.9-4.
122
5 Detailed Concepts and Implementation
6 Comparison with Related Approaches
123
6 Comparison with Related Approaches
This chapter describes other approaches and facilities that are similar or at least related to those
of PCoC.
6.1 Environmental Acquisition
This section gives an overview over the conventional acquisition mechanism called
environmental acquisition and how a simple implementation could look like. It explains
differences between implicit (automatic) and explicit acquisition.
We assume that the reader has read Section 5.4 as general introduction into the acquisition
concept.
The following sources relating to this topic are strongly recommended: [GIL96], [ACQU00],
[ACQU01], and some other acquisition documents [ACQU98].
6.1.1 Overview of Environmental Acquisition
Environmental acquisition is a mechanism that allows an object to obtain a requested feature
from its environment (containment hierarchy). It is used where the environment of objects or
components provides attributes or a context that has or can have an impact on the objects and
components.
Let us assume we are implementing an interactive component, for example, a source-code editor.
It may consist of different replaceable parts. One part could be the editor view, where we can edit
source code. Another might be a visual component showing line numbers. A third could be a
status information pane that shows the caret position and other status information. All of these
components have something in common: the file buffer and caret information. It makes sense to
make this information automatically accessible to all of the components so that they can use it as
if they themselves owned it.
Generally we can say that environmental acquisition is necessary to share data among
components in a containment hierarchy. The mechanism allows components to redefine
attributes they acquired from their environment. For some components it might make sense to
manage information or redefine some methods independently of their environment, or to wrap
shared information or functionality.
6.1.2 Environmental Acquisition in Literature
[GIL96] explains the motivation for introducing environmental acquisition as follows:
To give the reader a taste of the motivation for this research, consider the following example
which may occur in an automobile industry application: An object of a class Car depicted in
Figure 6.1-1 is a composite which comprises components such as objects of class Door.
124
6 Comparison with Related Approaches
Color
1
Car
Sedan
1
*
Door
Hatchback
Figure 6.1-1 Environmental acquisition in the problem space: Class
Door acquires its color from Sedans and Hatchbacks alike
Suppose that it is known that a car is colored red, then we are likely to infer that its doors are red
as well. However, although the door “inherits” its color from the car of which it is part, it would
be wrong to derive Door from Car. Color-inheritance is related to the “in-a” (reverse-“has-a”)
link which binds doors to cars.
This becomes also clear when examining Car subclasses, say Sedan and Hatchback, which
are distinguished among other things by the number of doors. Class Door inherits its color from
Sedan and Hatchback alike, just as it would from another hypothetical class Airplane
which has no “is-a” (“a-kind-of”) relationship with Car.
We call this kind of inheritance environmental acquisition and distinguish it from class-based
inheritance (inheritance for short) in its common meaning in object oriented programming.
[ACQU98], [ACQU00], and [ACQU01] are helpful for understanding the basics of
environmental acquisition.
6.1.3 Simple Implementation
The following code fragment shows a simple implementation of environmental acquisition.
Program 6.1-1 Acquisition (Java)
public class AcquisitionBase {
protected Object parent;
public AcquisitionBase() { parent = null; }
public printResult(String methodName) {
Object o = perform(methodName);
if (o != null) { System.out.println("Result: " + o); }
}
public Object perform(String methodName) {
Object result = null;
try {
Method m = getClass().getMethod(methodName, null);
result = m.invoke(this, null);
}
catch (Exception ex) {
if (parent != null) { result = parent.perform(methodName); }
else { System.err.println("no such method: "+methodName); }
}
return result;
}
}
In this example, we can dynamically invoke methods by using the method perform. The
specified method is looked up in the current object first, respectively in its class and its base
classes, and then in the parent object.
The example is based on Java reflection. See [ENGLA97] on Pages 40-42 for a short
introduction into Java reflection.
6.1 Environmental Acquisition
125
Program 6.1-2 Acquisition (Java)
public class A extends AcquisitionBase {
public A() {
}
public A(Object p) {
parent = p;
}
public String getAValue() {
return "A";
}
}
public class B extends AcquisitionBase {
public A a;
public B() {
a = new A(this);
}
public String getValue() {
return "B";
}
}
public class C extends AcquisitionBase {
public A a;
public C() {
a = new A(this);
}
public String getValue() {
return "C";
}
}
Program 6.1-1 and Program 6.1-2 show a simple implementation of an environmental acquisition
mechanism for invoking methods. AcquisitionBase is a class introducing the method
perform for dynamically invoking methods using the Java reflection mechanism and providing
implicit (automatic) environmental acquisition.
If a method cannot be found in the current object, the request for invoking a method named
methodName is delegated to its container. perform succeeds if an object is found providing a
corresponding method. It fails if the root object in the containment hierarchy is found without
finding a method with the given name.
Note that with the given implementation it is only possible to invoke methods without
parameters using the perform method, but it should be enough to provide a rough overview of
how acquisition works.
Let us take a look at a use case for our example above:
Program 6.1-3 Using Acquisition (Java)
A a = new A();
B b = new B();
C c = new C();
b.a.printResult("getValue");
--> "Result: B"
c.a.printResult("getValue");
--> "Result: C"
a.printResult("getValue");
--> "no such method: getValue"
a.printResult("getAValue");
--> "Result: A"
If method getValue is invoked on b.a or c.a, it is found in the container
AcquisitionBase. If it is invoked directly on a, which does not provide the method and no
container which could provide it, the call results in an error.
126
6 Comparison with Related Approaches
The Python programming language (see [ROSSUM90]) provides much more flexible usage of
acquisition than Java, since no method or attribute declarations are necessary. The following
code fragment shows the built-in acquisition mechanism of Python.
Program 6.1-4 Acquisition (Python)
class AcquisitionBase:
def __getattr__(self, attribName):
if self.parent:
return getattr(self.parent, attribName)
class A(AcquisitionBase):
def __init__(self, parent=0):
self.parent=parent
def getAValue(self):
return "A"
class B(AcquisitionBase):
def __init__(self):
self.a=A(self)
def getValue(self):
return "B"
class C(AcquisitionBase):
def __init__(self):
self.a=A(self)
def getValue(self):
return "C"
If Python does not find a requested attribute or operation either in the class of the given object or
in one of its base classes, it tries to find the method __getattr__. In this method we can
define what Python should do in this case, for example, search for the method in the parent
object, as in the example above.
The example code for utilizing acquisition looks similar to that in Java:
Program 6.1-5 Using Acquisition (Python)
a = A();
b = B();
c = C();
print 'Result: ' + b.a.getValue()
--> "Result: B"
print 'Result: ' + c.a.getValue()
--> "Result: C"
print 'Result: ' + a.getValue()
--> runtime error (TypeError: 'NoneType' object is not callable)
print 'Result: ' + a.getAValue()
--> "Result: A"
If a method getValue is invoked on b.a or c.a, it is found in the container. If it is invoked
directly on a, which does not provide the method and no container which could provide it, the
call results in an error.
Note that acquisition is not only possible with methods, but also with attributes.
6.1.4 Implicit Acquisition
We have seen what environmental acquisition is in general. Now, let us take a look at the
differences between implicit and explicit acquisition.
Implicit acquisition automatically searches for attributes and methods in the environment
whenever they cannot be obtained directly from an object or through class inheritance.
6.1 Environmental Acquisition
127
According to the section above, we call it implicit or automatic acquisition of method calls,
whenever we invoke methods using perform of AcquisitionBase. If a method is called
this way, it is first searched in the class or base classes of the object itself, and then of the parent
object, and so on (cf. Program 6.1-1 and Program 6.1-2).
6.1.5 Explicit Acquisition
When explicit acquisition is used, attributes are not automatically obtained from the
environment. Instead, an acquire method must be used.
Let us assume, we invoke methods as usual (ordinary method calls), without using acquisition. In
some cases, though, it is useful to use the acquisition mechanism. In these cases we can explicitly
use method perform (see Program 6.1-1) which searches and invokes specified methods in the
environmental context of the specified object.
Program 6.1-6 Using Explicit Acquisition (Java)
a = A();
b = B();
c = C();
System.out.println("Result:
--> "Result: A"
System.out.println("Result:
--> "Result: B"
System.out.println("Result:
--> "Result: B"
System.out.println("Result:
--> "Result: C"
" + b.a.getAValue());
" + b.getValue());
" + b.a.perform("getValue"));
" + c.a.perform("getValue"));
In the example above, the methods b.a.getAValue() and b.getValue() are called
directly.
The
other
calls
use
environmental
acquisition,
e.g.,
for
b.a.perform("getValue") the method getValue is searched in b.a, respectively in its
base classes, and then in b, where it is finally invoked.
The main difference between explicit and implicit acquisition is that one can decide for each
method to invoke it normally or using environmental acquisition (using method acquire).
6.1.6 Remarks
Environmental acquisition is, for instance, used and promoted by Zope—a software system
combining a Web-server and a content management system.
Following sources cover concepts of environmental acquisition: [GIL96], [ACQU00],
[ACQU01], and [ACQU98]. Environmental acquisition is compared to a similar approach of
PCoC in Section 5.4.
6.2 Aspect Oriented Programming
This section provides an rough overview of aspect oriented programming and illustrates this
software-technical paradigm with a few examples.
6.2.1 Overview
Aspect-oriented programming (AOP) is a programming technique first defined by Xerox Palo
Alto Research Center at the beginning of 1997. See [XEROX0297], [XEROX1297], and
[XEROX0697]. Some sources say that the term already came up in OOPSLA circles in 1995.
128
6 Comparison with Related Approaches
Traditional software units of modularity in programming languages include modules, classes, and
methods. However, some functionality cannot be encapsulated in single modules. Some units are
multiply implemented across the class hierarchy.
Aspect-oriented programming (AOP) brings new units of modularity, called aspects. Aspects are
constructs, respectively implementations as classes, representing factors of behavior (features)
and can be involved in more than one object or component. AOP builds on traditional
technologies, including procedural and object-oriented programming, which have already made
significant improvements in software modularity.
Aspects eliminate object and component borders. Code relating to aspects is often expressed as
small code fragments tangled and scattered throughout several components. Because of the way
they cross module boundaries, it is said that aspects crosscut the program’s hierarchical structure.
Aspects encapsulate crosscutting concerns.
As mentioned, aspects can be invoked on different parts of a system, and they are not limited to
public interfaces of objects and components.
Typical aspects are:
v tracing/logging/monitoring
v error/exception handling
v synchronization/thread-safety
v caching strategies
v persistency
v resource sharing
v distribution concerns
v optimizations
v etc.
Aspect compilers are used to generate code from actual source code and aspects. When using
such language extensions, one does not actually see the existence of aspects in the source code
where they are later invoked. The code produced by the aspect compiler can then be compiled by
a normal compiler. The term “code-weaver” is often used for this kind of compiler to illustrate
what it does. However, AOP is also possible without preprocessors. Aspects can, for instance, be
invoked through templates, name spaces, and inheritance (see later). PCoC, for example,
supports crosscutting concerns at runtime (cf. Sections 2.8 and 5.4).
An example for an aspect-oriented programming language is AspectJ (see [KICZAL00]). It is a
general-purpose AOP extension to Java and product of years of research at Xerox PARC. The
project is partially supported by the Defense Advanced Research Projects Agency (DARPA).
AspectJ enables the modularization of crosscutting concerns as described above. A
corresponding aspect compiler is called AspectWeaverTM (ajc). It runs right before the Java
compiler.
The main design goals of AspectJ were to make it a compatible extension to Java and as simple
as possible.
Compatibility includes:
v Upward compatibility: all regular Java programs must also be regular AspectJ programs.
v Platform compatibility: regular AspectJ programs must run on standard Java virtual
machines.
6.2 Aspect Oriented Programming
129
v Tool compatibility: it must be possible to work with AspectJ using existing tools, including
IDEs, documentation and design tools.
v Programmer compatibility: programming with AspectJ must feel like natural extension of
programming with Java.
AOP is said to become a powerful means of coping with the rising complexity of modern
software applications. Enabling highly modular software, it holds the potential for simplifying
the development of complex software applications, for improving the reuse of code fragments,
and finally for reducing maintenance efforts and costs.
A good introduction into aspect-oriented programming can be found in [CZARN01]—an article
in the iX magazine of 2001—and in subsequent articles of the series. Other useful sources for
this section were [LUTZ01] and [KICZAL00].
6.2.2 AspectJ
A critical element in the design of any aspect-oriented programming language is the join point
model. The join point model provides the frame of reference that makes it possible for execution
of a program’s aspect, and non-aspect code to be coordinated properly.
AspectJ is based on a model in which so-called join points are nodes in a simple runtime object
call graph. These nodes include points at which an object receives a method call and points at
which a field of an object is referenced. The edges between nodes represent control flow
relations. In this model each node is visited twice—once when walking down the call graph for
subnodes, and once when coming back from the call of subnodes.
The following join points are possible (taken from [KICZAL00]):
join point
calling
method and
constructors
pointcut designator syntax
calls(<signature>)
explanation
a method or constructor of a class is
called. Call join points are in the
calling object or not related to an object
if a static method is called
reception of receptions(<signature>)
method and
constructor
calls
an object receives a method or
constructor call. Reception join points
are before method or constructor
dispatch, i.e., they happen inside the
called object. Control flow has already
been transferred to the called object,
but no particular constructor and
method has been called yet.
execution of executions(<signature>)
methods and
constructors
getting fields gets(<signature>)
an individual method or constructor is
invoked
a field (member variable) of an object,
gets(<signature>) [<value>] class or interface is read
setting fields sets(<signature>)
a field of an object or class is set
sets(<signature>) [<value>]
sets(<signature>)
[<oldvalue>] [<newvalue>]
exception
handling
handles(<throwable type
name>)
an exception handler is invoked
130
6 Comparison with Related Approaches
Note that pointcuts identify join points of specific types. Here are some more complex pointcut
designators:
join point
any
pointcut designator syntax
explanation
instanceof(<currently
matches join points if the currently
executing
object
type executing object is of the given type
name>)
any
within(<class name>)
matches join points if the the currently
executing code is contained within
class <class name>
any
withincode(<signature>)
matches join points if the the currently
executing code is contained within the
member defined by the method or
constructor signature
any
cflow(<pointcut
designator>)
matches join points of any kind that
occur within the dynamic extent of any
join point matched by <pointcut
designator>
method calls call(<pointcut designator>) matches method call join points that in
one step lead to any reception or
execution join points matched by
<pointcut designator>
Note that a pointcut is a set of join points that optionally exposes some of the values in the
execution context of these join points. Pointcut designators can be combined using AND, OR, and
NOT operators (‘&&’, ‘||’, ‘!’).
Before we take a look at using pointcuts, we define a simple class:
Program 6.2-1 Sample class
// A regular class
class MyPoint {
private int fx;
private int fy;
public void setX(int x) {
fx = x;
}
public void setY(int y) {
fy = y;
}
public int getX() {
return fx;
}
public int getY() {
return fy;
}
}
Now we can define a simple pointcut:
Program 6.2-2 A simple pointcut
pointcut myPointMoves():
receptions(void MyPoint.setX(int)) ||
receptions(void MyPoint.setY(int));
6.2 Aspect Oriented Programming
131
Pointcut myPointMoves identifies whenever the position of an object of class MyPoint is
changed.
Another facility of AspectJ is called advice. An advice is a method-like mechanism used to
declare that certain code should execute at each of the join points in the pointcut. AspectJ
supports before, after, and around advice.
before and after are quite self-explanatory. around is invoked even before the before
section. There can be many around sections for a join point, these are invoked with the most
specific piece first. The body of an around advice can call runNext which invokes the next
most specific piece, and if no other around body is left, before is invoked.
Here is a simple example of an after advice:
Program 6.2-3 A simple advice
static after(): myPointMoves() {
System.out.println("Point moved.");
}
Parameters can be passed to advices as well as to pointcuts. Read more about these constructs in
[KICZAL00] and [XEROX02].
Aspects are defined by aspect declarations, which are similar to class declarations. They may
include pointcut declarations, advice declarations, as well as all other kinds of declarations
permitted in class declarations.
Program 6.2-4 Aspects in AspectJ
// Define a tracing aspect for set methods and copy
aspect MyTracer {
pointcut tracedCalls():
calls(void MyPoint.set*(int)) && calls(void MyPoint.get*());
before(): tracedCalls() {
System.out.println("Invoking: " + thisJoinPoint)
}
}
In the example above we register all set and get methods of class MyPoint in the pointcut
tracedCalls.
The idea behind all this is to extend existing classes and methods by specific behavior and to
keep these extensions out of regular code. These units supplying the specific behavior are called
aspects. An aspect can cover different classes and methods at the same time, thus combining
their common functional behavior (aspect) at one location. This makes it not only easier to
maintain common aspects such as tracing or preconditions—it keeps code more readable. A
disadvantage is the need for additional compile runs using aspect parsing tools.
6.2.3 Other Mechanisms
Beside the most advanced and sophisticated tool AspectJ, there are some other mechanisms and
development tools available for aspect-oriented programming. One is the programming language
D, also developed by Xerox. Read more about it in [XEROX1297].
The idea behind all available approaches and tools is the same—to provide a mechanism for
weaving code. Different aspects of code can be developed and maintained separately.
[CZARN01] describes a concept for aspect-oriented programming with C++. It uses name spaces
to separate pure functional code from aspects.
132
6 Comparison with Related Approaches
The following example illustrates this approach:
Program 6.2-5 Defining aspects by using name spaces in C++ (1)
namespace original
{
// A regular class
class MyPoint {
private:
int fx;
int fy;
public:
MyPoint()
: fx(0), fy(0) { }
void setX(int x) { fx =
void setY(int y) { fy =
int getX() { return fx;
int getY() { return fy;
};
}
x; }
y; }
}
}
Note that we define the original class in the name space original. Having defined it, we can
go on and define an aspect for this class in another name space:
Program 6.2-6 Defining aspects by using name spaces in C++ (2)
namespace aspect
{
typedef original::MyPoint MyPoint;
// A regular class
class MyPointWithTracing : public original::MyPoint {
public:
void setX(int x) {
MyTracer::before("setX");
MyPoint::setX(x);
}
void setY(int y) {
MyTracer::before("setY");
MyPoint::setY(y);
}
int getX() { return MyPoint::getX(); }
int getY() { return MyPoint::getY(); }
};
class MyTracer {
static void before(char* str) { cout << "before " << str endl; }
static void after(char* str) { cout << "after " << str endl; }
}
}
In the program above, we first define MyPoint as original::MyPoint for simplification
of the rest of the code. We trace each call of setX and setY by invoking our tracer. Of course,
we then have to call the actual method of the base class.
Now that we have defined the aspect, we can make use of the class in any user code:
Program 6.2-7 Aspects using name spaces in C++ (3)
namespace composed
{
// typedef aspect::MyPoint MyPoint;
// original point class
typedef aspect::MyPointWithTracing MyPoint; // point class with tracing
}
// use that point class configured in name space "composed"
using namespace composed;
MyPoint p;
p.setX(10);
6.2 Aspect Oriented Programming
133
Note that in this case the base class original::MyPoint need not declare its methods as
virtual. The nice thing about this concept is that neither client code, nor code of the original class
is affected by the aspect. Aspects can be switched on and off just by configuration of the
composed name space (which itself can happen outside client code, maybe in a special header
file). It may not be as convenient as AspectJ, and it is not possible to define pointcuts, but it is a
way to introduce aspects in existing code without bothering with a language and tools for aspectoriented programming.
6.2.4 Remarks
Aspect-oriented programming (AOP) brings new units of modularity and thus reusability, called
aspects. Aspects are constructs, respectively implementations as classes, representing factors of
behavior (features) and can be involved in more than one object or component.
AOP is said to become a powerful means of coping with the rising complexity of modern
software applications. Enabling highly modular software, it holds the potential for simplifying
the development of complex software applications, for improving the reuse of code fragments,
and finally for reducing maintenance efforts and costs.
There are already real AOP solutions such as AspectJ. See [KICZAL00].
The PCoC framework supports crosscutting concerns at runtime, like AOP does at compile time.
Different aspects can dynamically wrap Activities (see Section 2.8) or Activity Sets (acquisition
in PCoC, see Section 5.4). Clients can also attach as listeners to Dispatchers and Activities, so
they get notified before and after the Dispatchers or Activities are invoked or their state is
changed (see Section 2.9). On notification, clients can take corresponding action.
Since operations (Activities) are objects in PCoC, they can be traced and extended by advice
mechanisms as in AspectJ. For example, a component developer has to override the method
doPerform of an Activity base class (cf. Program 4.3-15). Since doPerform is wrapped and
invoked by the base-class method perform, the framework can do a great deal of
administrative work before and after the invocation of actual user code. This approach is, for
example, used for tracing invocations of Activities for later replay through scripting (macro
recording and replay).
Currently, there is no sophisticated implementation of pointcut and join point registration and
evaluation in PCoC (except tracing Activity invocations for the purpose of macro recording).
However, it is possible to attach listeners to Dispatchers. They can provide execution state
information (before execution, executing, after execution) and other reflective information
(current Activity Set, etc.) The reflection facilities together with the listener support can be used
to simulate pointcuts for the invocation of Activities.
Read more about aspect-oriented
[XEROX0697], and [KICZAL00].
programming
in
[XEROX0297],
[XEROX1297],
6.3 Event Channeling
Events are an important communication mechanism for many domains. We distinguish between
directed events where the receiver of an event is specified by its sender, and event channels.
An event channel is a channel at which interested receivers can register. The sender does not
normally know the actual receivers. The advantage of this concept is that it can be used to
decouple involved objects.
However, distribution and handling of events over event channels also leads to some problems.
For example, it may be difficult to find out the sender of an event, or the reason why it was
triggered. There is also an increased effort to handle different events that are sent through the
134
6 Comparison with Related Approaches
same event channel. Generally, analyzing and dispatching an event via event-channeling may
require more effort on the receiver-side than for directed events.
A solution can be a mediator instance within an event channel that selects and invokes special
callbacks on receivers. Such a mediator can be a table that associates events with concrete
methods. PCoC has a similar approach. A request is directly delegated to Activities on receiver
side, which are associated with the request. More precisely the method perform of each
associated Activity is called, when a Dispatcher delegates a corresponding request. An Activities
Provider can register at a kind of event channel or even create it by adding an own Activity to an
Activity Set that is in the acquisition branch of the Dispatcher (via proper dispatch directives). In
order to make an Activity Set and a corresponding Activity reachable to a Dispatcher, either the
Activity Set must be acquired by one that is already in the current acquisition branch of the
Dispatcher, or the directives must be properly adapted. No analyzing and dispatching is necessary
on the receiver side. Read more about the dispatch mechanism of PCoC in Section 5.6.2.
Events and especially event channeling are described in great detail in [GRIFF98] on Pages
254ff.
6.4 Java Reflection
This section describes Java reflection facilities such as dynamic method invocation, dynamic
class lookup and instantiation, and proxy class. Concepts are compared to those of PCoC.
6.4.1 Overview
The Java programming language provides a facility called reflection. It allows an executing Java
program to examine (introspect) itself, and to manipulate internal properties of the program. For
example, it is possible for a Java class to obtain the names of all its members.
The ability to examine and even manipulate a class from within itself may not sound impressive,
but in other programming languages this feature does not exist. For example, there is no way in a
Pascal, C, or C++ program to obtain information about the functions defined within that
program. Microsoft’s .NET introduces reflection facilities for its supported programming
languages: Visual Basic, Managed C++, C#, and Microsoft’s version of Java. Currently,
developers are restricted to Microsoft’s compiler and their operating systems to make use of
.NET and its facilities. See also Microsoft’s .NET Readiness Kit, [MICROS01].
One tangible use of reflection is in JavaBeans where software components can be manipulated
visually via a builder tool. The tool uses reflection to obtain the properties of Java components
(classes) as they are dynamically loaded.
Compare Java reflection to .NET reflection facilities in Sections 6.7.6 and 6.7.5.
6.4.2 Reflecting Classes and Methods in Java
The class Class is a meta-class that holds information about other classes. It can be used to
instantiate objects of a given class. The following two accesses are probably the ones used most
often:
Program 6.4-1 Retrieving a class object
Class cl = Class.forName("MyClass");
Class cl = MyClass.class;
These are almost equivalent in meaning, however, the first statement throws an exception if the
specified class does not exist (ClassNotFoundException). In either case cl gets a
reference to a class, which can then be used to look up the methods of the class.
6.4 Java Reflection
135
For primitive data types the corresponding class can be retrieved as follows:
Program 6.4-2 Retrieving a class object for primitive data types
Class c = int.class;
Class c = Integer.TYPE;
Both statements are equivalent. Primitive data types are not really relevant here; these approaches
are mentioned for completeness.
The following non-static methods of Class are the most interesting for dynamic method
invocation:
Program 6.4-3 Retrieving method objects
public Method getMethod(String name, Class[] parameterTypes);
public Method[] getMethods();
getMethod returns a Method object that reflects the specified public member method of the
class or interface represented by this Class object.
getMethods returns an array containing Method objects reflecting all the public member
methods of the class or interface represented by this Class object, including those declared by
the class or interface and and those inherited from base classes and super-interfaces.
There are similar methods getDeclaredMethod and getDeclaredMethods that return
only methods declared in the given class, but not in one of its base classes or base interfaces.
The JDK 1.2 API documentation [JDK12] provides more detailed description of these methods.
6.4.3 Java Type Checking
We may want to check whether an object is an instance of a specific class.
Program 6.4-4 Checking the class of an object
Class cl = Class.forName("MyClass"); // or cl = MyClass.class
boolean classOK = MyClass.class.isInstance(new Integer(10));
System.out.println(classOK); // output is false
boolean classOK = MyClass.class.isInstance(new MyClass());
System.out.println(classOK); // output is true
In this example, a Class object for MyClass is created, and then some objects are checked to
see whether they are instances of MyClass or of any of its subclasses. new Integer(10) is
not, but new MyClass() is. The method isInstance is the dynamic equivalent of the
instanceof operator.
6.4.4 Dynamic Method Invocation in Java
In Java, methods can be invoked dynamically by using method invoke of class Method. This
feature is illustrated in the the following example. Note that the Method class is in the Java
reflection package java.lang.reflect.
Program 6.4-5 shows an application of Java reflection on a String object (string). We specify
the kind of method we want, which is in this case the method concat with parameter types
parameterTypes. getMethod returns a method reference to a corresponding method, or
throws an exception if an error occurs. With invoke we specify the object where method m
should be invoked, and the arguments that should be passed to the method—in this case the
string “def”. Finally, we print the result.
136
6 Comparison with Related Approaches
Program 6.4-5 Dynamic method invocation
String string = "abc";
String result = null;
Class[] parameterTypes = new Class[] {String.class};
Object[] arguments = new Object[] {"def"};
try {
Method m = String.class.getMethod("concat", parameterTypes);
result = (String) m.invoke(string, arguments);
}
catch (NoSuchMethodException e) { System.out.println(e); }
catch (IllegalAccessException e) { System.out.println(e); }
catch (InvocationTargetException e) { System.out.println(e); }
System.out.println("Result: " + result);
Read more about dynamic method invocation on the .NET platform in Section 6.7.6.
6.4.5 Java Reflection: Invoking Constructors
Invocation of constructors is slightly different to that of methods. An example for constructor
invocation is given below:
Program 6.4-6 Invoking constructors
import java.lang.reflect.*;
public class MyClass {
public MyClass() {
}
public MyClass(int x) {
}
public static void main(String[] args) {
try {
Class cl = Class.forName("MyClass");
Class[] paramaterTypes = new Class[1];
paramaterTypes[0] = Integer.TYPE;
Constructor ct = cl.getConstructor(paramaterTypes);
Object[] arglist = new Object[1];
arglist[0] = new Integer(10);
Object retobj = ct.newInstance(arglist);
}
catch (Throwable e) {
System.err.println(e);
}
}
}
First, we get the class reference for class MyClass. Then, we specify the parameter types of the
constructor we want to get, which is in this case a constructor with a single int parameter. We
retrieve the constructor, and use it to create a new instance of MyClass, and pass an Integer
object with the value 10.
6.4.6 Java Reflection: Fields
Another feature of the Java reflection API is the possibility to inspect fields and to modify their
values at runtime. This is illustrated in the following example.
6.4 Java Reflection
137
Program 6.4-7 Retrieving fields
import java.lang.reflect.*;
public class MyField {
public int val;
public MyField() { val = 10; }
public static void main(String[] args) {
try {
Class cl = Class.forName("MyField");
Field f = cl.getField("val");
MyField fObj = new MyField();
System.out.println("val = " + fObj.val); // output is 10
f.setInteger(fObj, 20);
System.out.println("val = " + fObj.val); // output is 20
}
catch (Throwable e) { System.err.println(e); }
}
}
We retrieve the int field val and set its value from 10 to 20.
This feature is needed for JavaBeans where it is possible to change the value of a field via
introspection in the GUI of a JavaBean.
6.4.7 Dynamic Classes in Java: Class Proxy
All the software constructs of the reflection package presented so far have one thing in
common—they cannot be assembled at runtime. They can only be inspected. In the case of fields,
also the content can be modified. This may, however, not always be sufficient.
In Java 1.3, Sun introduced a new class called Proxy, which allows dynamically assembling
classes, so-called dynamic proxy classes, or proxy classes for short. This section describes the
class and its use.
Sources for information about Dynamic Proxies are [JDK13P], [JDK13D], and [BLOSS00]. The
following sections contain large excerpts of these documents.
6.4.7.1 Overview
Proxy allows assembling a class at runtime, a so-called (dynamic) proxy class. A list of
interfaces specifies which interfaces the proxy class implements. Method invocations on the
proxy class are dispatched to an invocation handler.
The class Proxy is derived from Object:
Program 6.4-8 Class Proxy
public class Proxy
extends Object
implements Serializable { ... }
A proxy class can be created with the following static method of Proxy, given a list of
interfaces and a class loader.
138
6 Comparison with Related Approaches
Program 6.4-9 Method for creating Proxy classes
public class Proxy
extends Object
implements Serializable {
...
public static Class getProxyClass(ClassLoader loader, Class[] interfaces)
throws IllegalArgumentException {
...
}
}
The list of classes contains only interfaces, not classes or primitive types. The specified
interfaces must be visible by name through the class loader.
All non-public interfaces must be in the same package. There must not be methods with the same
name and parameter lists and different return values in the given interfaces.
Given the interface:
Program 6.4-10 Sample interface
public interface IPoint {
void setLocation(int x, int y);
}
we can create a proxy class as follows:
Program 6.4-11 Creating a Proxy class
Class proxyClass = Proxy.getProxyClass(IPoint.class.getClassLoader(),
new Class[] {IPoint.class});
This proxy class implements interface IPoint. Note that the methods of interfaces for a proxy
class also support primitive data types as parameters. They are mapped to corresponding object
types, e.g. int becomes Integer, boolean becomes Boolean, etc.
A proxy class itself cannot do much. For instantiation of the class we need an invocation handler
to dispatch method calls invoked on the proxy class.
InvocationHandler is an interface that declares only the following method:
Program 6.4-12 InvocationHandler interface
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
invoke is called whenever a method is invoked on an associated proxy instance. The proxy
instance passes itself, the invoked method, and the argument list of the invoked method as
arguments.
6.4 Java Reflection
139
Given the invocation handler:
Program 6.4-13 Sample invocation handler
public class PointProxyHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
try {
System.out.println("method: " + method.getName());
String str = "arguments:";
for (int i = 0; i < args.length; i++) { str = str + " " + args[i]; }
System.out.println(str);
}
catch (InvocationTargetException e) { // an invoked method threw an exception
throw e.getTargetException();
// forward this exception
}
catch (Exception e) {
throw new RuntimeException("Unexpected exception " + e.getMessage());
}
finally { System.out.println("after method: " + method.getName()); }
}
}
and the proxy class of Program 6.4-11, we can create a proxy instance as follows:
Program 6.4-14 Creation of a Proxy instance (1)
Class proxyClass = Proxy.getProxyClass(IPoint.class.getClassLoader(),
new Class[] {IPoint.class});
IPoint pointProxy = (IPoint) proxyClass.
getConstructor(new Class[] { InvocationHandler.class }).
newInstance(new Object[] { new PointProxyHandler() });
Mostly the proxy class object is not needed; one can use a simpler method of Proxy, combining
proxy definition and instantiation:
Program 6.4-15 Creation of a Proxy instance(2)
IPoint pointProxy = (IPoint) Proxy.newProxyInstance(IPoint.class.getClassLoader(),
new Class[] {IPoint.class},
new PointProxyHandler() });
Now we can invoke methods on our proxy instance:
Program 6.4-16 Invocation of a Proxy instance
pointProxy.setLocation(10, 20);
if (pointProxy instanceof IPoint) { System.out.println("ok"); }
In this case, the invocation of all methods on pointProxy will be dispatched to
PointProxyHandler. This includes, besides setLocation, the methods hashCode,
toString, and equals which are declared in java.lang.Object.
The cast operation (IPoint) will succeed and not throw a ClassCastException. The
proxy can be casted to all interfaces specified at proxy creation.
instanceof will also return true for all interfaces that have been specified for a proxy class.
140
6 Comparison with Related Approaches
Sometimes we may want the invocation handler, in our case PointProxyHandler, to
dispatch method calls to a special object:
Program 6.4-17 Invocation handler forwarding method calls
public class PointProxyHandler implements InvocationHandler {
Object point;
public PointProxyHandler(Object point) {
this.point = point;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result;
try {
result = method.invoke(point, args);
}
catch (InvocationTargetException e) { // an invoked method threw an
throw e.getTargetException();
// exception; forward this exception
}
catch (Exception e) {
throw new RuntimeException("Unexpected exception " + e.getMessage());
}
finally {
}
return result;
}
}
The object that should actually perform invoked methods is passed as an argument with the
constructor. In any case, the invocation handler of a proxy class either dispatches any method
calls to various objects or directly provides the implementation for the methods declared in the
interfaces added to the associated proxy. With PCoC, a Dispatcher only delegates requests for the
invocation of Activities with the same name (and signature) as the Dispatcher. Each request is
delegated to Activities. An Activity implements a single operation.
If we call any method in the invocation handler of a proxy, we have to pass the proxy instance as
receiver in order to use delegation rather than forwarding. For example,
otherMethod.invoke(proxy, args).
Given the class implementing the IPoint interface
Program 6.4-18 Interface implementation for a Proxy instance
public class PointImpl implements IPoint {
int x;
int y;
void setLocation(int x, int y) {
this.x = x;
this.y = y;
}
public String toString() { ... }
public boolean equals(Object obj) { ... }
...
}
the creation of a proxy will look as follows:
Program 6.4-19 Invocation of a method using a Proxy instance
IPoint pointProxy = (IPoint) Proxy.newProxyInstance(IPoint.class.getClassLoader(),
new Class[] {IPoint.class},
new PointProxyHandler(new PointImpl()) });
pointProxy.setLocation(10, 20);
if (pointProxy instanceof IPoint) { System.out.println("ok"); }
6.4 Java Reflection
141
In this case, the invocation of all methods on pointProxy will be dispatched to
PointProxyHandler, respectively to PointImpl. This includes, besides setLocation
methods hashCode, toString, and equals which are declared in java.lang.Object.
The cast operation (IPoint) will succeed, and not throw a ClassCastException. The
proxy can be casted to all interfaces specified at proxy creation.
instanceof will also return true for all interfaces specified for a proxy class.
6.4.7.2 Methods Duplicated in Multiple Proxy Interfaces
When two or more interfaces of a proxy class contain a method with the same name and
parameter signature, the order of the proxy class’s interfaces becomes significant. When such a
duplicate method is invoked on a proxy instance, the Method object passed to the invocation
handler will not necessarily be the one whose declaring class is assignable from the reference
type of the interface that the proxy’s method was invoked through. This limitation exists because
the corresponding method implementation in the generated proxy class cannot determine which
interface it was invoked through. Therefore, when a duplicate method is invoked on a proxy
instance, the Method object for the method in the foremost interface that contains the method
(either directly or inherited through a superinterface) in the proxy class’s list of interfaces is
passed to the invocation handler’s invoke method, regardless of the reference type through
which the method invocation occurred.
If a proxy interface contains a method with the same name and parameter signature as the
hashCode, equals, or toString methods of java.lang.Object, when such a method
is invoked on a proxy instance, the Method object passed to the invocation handler will have
java.lang.Object as its declaring class. In other words, the public, non-final methods of
java.lang.Object logically precede all of the proxy interfaces for the determination of
which Method object to pass to the invocation handler.
PCoC has a similar model for dispatching requests to Activities with the same name and
signature, acquired from different Activity Sets. Let us assume, an Activity Set A has acquired
Dispatchers with the name copy from the Activity Set B and then from C (in this order). When
the copy Dispatcher is invoked in A, it delegates requests with directive
PCCDirectives.first to an Activity in (or acquired by) the first acquired Activity Set B.
With directive PCCDirectives.broadcast, requests are delegated to B and C. The ranking
of the acquired Activity Sets can be changed at any time, for example, upon focus changes in the
GUI, etc. See Sections 5.4.3 and 5.6.2.
6.4.7.3 Remarks
Proxy allows the assembling of so-called proxy classes at runtime from a list of interfaces. By
providing an invocation handler one can create a proxy instance from a proxy class. A proxy
instance will behave like an instance of a real class. It is allowed to call all methods declared in
the specified interfaces. These method calls will be dispatched to the specified invocation
handler, as well as those of the methods hashCode, equals, or toString declared in
java.lang.Object. The operator instanceof on a proxy instance returns true, if the
corresponding proxy class implements the specified interface.
Although this facility makes Java more dynamic, it also has its limitations. For example, as
already mentioned, a list of interfaces has to be specified. This means that proxy classes are not
fully dynamic. So, although they are assembled at runtime, it is not possible to add classes and
instances at runtime where interfaces do not exist at compile time.
PCoC has a slightly different approach; it allows adding operations, respectively Activities, to a
component at runtime. Imagine a source-code editor Tool providing only the following basic
operations:
loadFile,
saveFile,
cut, copy,
paste,
getSelection,
142
6 Comparison with Related Approaches
setSelection, insert. Say, we want to write an extension to this editor, but do not have
the source code (or do not want to pay additional license fees). We may just want to reduce
compile time, and the freedom to change the source code without forcing third-parties to rebuild
everything. Anyway, let us assume, we want to add some code cleanup features to the editor, that
allow formatting source code following some coding conventions. In this case the following new
operations have to be introduced (they are self-explanatory): indentLine, comment,
reformat. PCoC allows to add these Activities to the editor at runtime. Users of the editor
would think these Activities originally were provided by the editor itself. The Activities belong
to the same Activity Set as the editor, but they may be created by another, external component
connected to the application via XMLRPC over TCP/IP (locally or remote).
Other advantages compared to proxy classes are the possibility to change the ranking of acquired
Activity Sets, respectively their Dispatchers, at runtime, the support for multicasts, and no need
to define interface classes. Java Proxies and PCoC Dispatchers both support delegation. An
Activity can send requests to the original receiver (Activity Set) where the Activity has been
requested. When an invocation handler of a proxy uses the proxy instance as receiver for further
method calls, we also have delegation. Note that the proxy instance where a method has been
invoked on is passed as first argument to an invocation handler. See Program 6.4-13.
The advantages of proxy classes are a lower memory consumption, better performance, and their
simple use—methods of proxy class instances are invoked like instance methods of any other
classes:
pointProxy.setLocation(10, 20)
In contrast, with PCoC, we have to use Dispatcher requests and to pack arguments into an
argument container:
activitySet.perform("|MyPoint|setLocation",
(new PCCMaterial(10)).add(20))
6.5 Java Swing Actions
This section presents the Java Swing action pattern and some typical use cases, and discusses
differences to PCoC. We will see how to use actions to share commands among different user
interface components such as menu items and toolbar buttons. We will also see how to change
status info when the mouse moves over action-based regions of a component. Another example
will show us how action events can be forwarded from one class to another. Finally, this section
discusses differences between Java actions and PCoC constructs such as Activities, Dispatchers,
and Tasks.
This section refers mainly to the following articles: [DAVID00], and [TOEDT01] on Pages 32ff.
For a more detailed description of Java Swing actions, see [WALRA99] on Pages 389ff, and
459ff.
6.5.1 Overview
A Java Swing action is an implementation of the command design pattern. It separates the
controller logic of a command from a visual representation such as menu items or toolbar
buttons. This is useful because the look of a menu or toolbar item can be changed just by
changing its configuration, instead of making the changes in source code. Actions support
property changes, for example, their short description, their state (“Enabled”, “Disabled”), etc.
So far, Swing actions are similar to PCoC Tasks together with the Dispatchers it uses and
Activities—the classes that actually implement operations. Before getting too deep into detailed
differences, take a more detailed look at Java Actions.
6.5 Java Swing Actions
143
6.5.2 Using Java Swing Actions
The javax.swing.Action interface extends ActionListener. It inherits the method
declaration of actionPerformed from ActionListener.
Program 6.5-1 ActionListener interface
public interface ActionListener extends EventListener {
public void actionPerformed(ActionEvent e);
}
This method is invoked when an ActionEvent occurs, and an object of a class implementing
this interface has been registered with a Java component, using the component’s
addActionListener method.
Program 6.5-2 Using ActionListeners
JMenuItem menuItem = new JMenuItem("Foo");
public class MyActionListener implements ActionListener {
public MyActionListener(JMenuItem menuItem) {
menuItem.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
System.out.println("actionPerformed triggered by ", e.getSource);
}
}
Later we will see that an Action implicitly becomes an ActionListener of menus, menu
items, or toolbar buttons where the Action instance is added.
Now to the actual Action interface:
Program 6.5-3 Action interface
public interface
public static
public static
public static
public static
public static
public static
public static
public static
Action extends ActionListener {
final String DEFAULT = "Default";
final String NAME = "Name";
final String SHORT_DESCRIPTION = "ShortDescription";
final String LONG_DESCRIPTION = "LongDescription";
final String SMALL_ICON = "SmallIcon";
final String ACTION_COMMAND_KEY = "ActionCommandKey";
final String ACCELERATOR_KEY = "AcceleratorKey";
final String MNEMONIC_KEY = "MnemonicKey";
public Object getValue(String key);
public void putValue(String key, Object value);
public void setEnabled(boolean b);
public boolean isEnabled();
public void addPropertyChangeListener(PropertyChangeListener l);
public void removePropertyChangeListener(PropertyChangeListener l);
}
In addition to actionPerformed, the Action interface adds some string definitions and
method declarations.
It defines a set of keys, such as Action.SHORT_DESCRIPTION, where a key represents the
name of a property of an action instance.
These keys together with the methods putValue and getValue are used to set and examine
properties such as descriptions, icon, and associated keystrokes.
144
6 Comparison with Related Approaches
Here, the meanings of some keys:
v DEFAULT is a constant that can be used as a key for getValue or as a value for
putValue calls for text properties and icons.
v SMALL_ICON is used for a small icon of an action in toolbar buttons.
v ACTION_COMMAND_KEY is used to determine the command string for the
ActionEvent that will be created when an action is going to be notified as the result of
residing in a KeyMap associated with a JComponent.
There are many others; most rather self-explanatory.
Now let us use the property methods of Action. Since we cannot instantiate an interface, we
use AbstractAction. This class implements the Action interface and adds property change
support and storage and retrieval for key values. All we have to do is to subclass
AbstractAction to add specific key values (name, description, or icon) and implement the
actionPerformed method.
For example, the property value of an action instance can be set as follows:
Program 6.5-4 Using actions (1)
Action copyAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
System.out.println("copy action triggered by ", e.getSource);
}
}
copyAction.putValue(Action.SHORT_DESCRIPTION, "Copy selection");
The following code fragment does the same as that in the example above:
Program 6.5-5 Using actions (2)
Action copyAction = new AbstractAction("Copy selection") {
public void actionPerformed(ActionEvent e) {
System.out.println("copy action triggered by ", e.getSource);
}
}
Note that the domain where actions are used is the same as that of PCoC Tasks (see Sections 4.5
and 5.7). Like actions, Tasks also provide a listener mechanism (see Section 4.3.2). The sender
of a message is passed with a PCCSenderInfo instance.
Now back to actions. Action listeners attached with addPropertyChangeListener are
notified whenever a property is changed using putValue (cf. Program 6.5-4), or by setting the
state
property
using
setEnabled.
Listeners
can
be
detached
using
removePropertyChangeListener.
All objects that have been added as listeners to an action will have a property-change listener;
this will response to the PropertyChangeEvent sent by the action and change the state of
the component.
Program 6.5-6 PropertyChangeListener interface
public interface PropertyChangeListener extends EventListener {
public void propertyChange(PropertyChangeEvent evt);
}
isEnabled and setEnabled are used to access the state of an action. Components, such as
menu items or toolbar buttons, which have been attached to the same action will share some
6.5 Java Swing Actions
145
properties. Changes will be propagated to all listeners. For example, if the action becomes
disabled by calling setEnabled(false), then all components that use this action will also
be disabled.
Say, we have an action attached to a menu item, and change the state from “Enabled” to
“Disabled” using setEnabled(false), the menu item will change its look appropriately
upon a property change notification. For example, AbstractButton, a base class of
JMenuItem, and JMenuItem provide a method for creating a property-change listener that
changes the look of the control to enabled or disabled if the state of an attached action changes.
PCoC Tasks provide a generic state concept for Activities (see Section 4.3.7). There are some
predefined states, such as “Disabled”, and “Enabled”. However, Activity states can be defined
arbitrarily, since they are simple strings. Another useful state is, for example, “Active”, denoting
a checkmarked state of a menu item.
Some components, such as JButton and JMenuItem, have constructors that take an action as
argument and use its properties for their construction. Some Swing container components, such
as JToolBar and JMenu, have add methods that take an action as an argument and create a
component, such as JMenuItem, from that action.
6.5.3 Using Actions for Forwarding
For actions that extend AbstractAction, we only have to implement the
actionPerformed method. As described earlier, an action may be attached as an
ActionListener to other objects, such as menu items, toolbar buttons, etc. The event source
calls actionPerformed for all attached listeners and passes an action event instance which
holds information such as the source of the event. The distribution of an action event may be
initiated by a user selecting a menu item of which the action is a listener.
In some cases we may want to forward an action event to an intermediate object instead if
handling the event in the action itself. The delegates of the action would do the actual work,
while the action would be used only as a dispatch instance. This concept makes sense if behavior
and/or data must be shared among many actions, such as editing commands (copy, paste), or
if an abstraction layer is needed for services, such as file system operations.
Program 6.5-7 Command dispatch using action events
public MyDispatchAction extends AbstractAction {
public MyDispatchAction(String name) {
super(name);
putValue(Action.ACTION_COMMAND_KEY, name);
}
void addActionListener(ActionListener listener) {
if (fListeners != null) {
fListeners = new EventListenerList;
}
fListeners.add(ActionListener.class, listener);
}
void removeActionListener(ActionListener listener) {
if (fListeners == null) {
return;
}
fListeners.remove(ActionListener.class, listener);
if (fListeners.length == 0) {
fListeners = null;
}
}
... // see below
The following method is called when an action is invoked.
146
6 Comparison with Related Approaches
Program 6.5-8 Dispatching an action event
...
// see above
/** Forwards the ActionEvent to all registered listeners.
*
public void actionPerformed(ActionEvent evt) {
if (fListeners != null) {
Object[] listeners = fListeners.getListenerList();
// Create a new ActionEvent with the original source and the
// command property associated with this action
ActionEvent e = new ActionEvent(evt.getSource(), evt.getID(),
(String)getValue(Action.ACTION_COMMAND_KEY));
// forward the new action event object to all listeners
for (int i = 0; i <= listeners.length-2; i+=2) {
((ActionListener)listeners[i+1]).actionPerformed(e);
}
}
}
}
private EventListenerList fListeners;
// MyDispatchAction
In Program 6.5-7 and Program 6.5-8 we generate a new action event object and forward it to all
listeners of this action.
The next example shows how such dispatcher actions can be used.
Program 6.5-9 Using dispatch actions
public class MyMenuManager {
public MyMenuManager(MyActionProvider provider) {
fMenuBar = new JMenuBar();
JPopupMenu menu = new JPopupMenu();
ArrayList actions = provider.getActions();
for (int i=0; i<actions.size(); i++) {
menu.add(actions.get(i)); // add action provider actions
}
fMenuBar.add(menu);
}
public JMenuBar getMenuBar() { return fMenuBar; }
JMenuBar menuBar;
}
public class MyActionDelegate implements ActionListener {
public void actionPerformed(ActionEvent evt) {
String command = evt.getActionCommand().toLowerCase();
if (command.equals("cut")) { ... }
// do the actual work
else if (command.equals("copy")) { ... }
else if (command.equals("paste")) { ... }
}
}
public class MyActionProvider {
public MyActionProvider() {
fActions = new ArrayList();
fActions.add(new MyDispatchAction("Cut")); // create some dispatch actions
fActions.add(new MyDispatchAction("Copy"));
fActions.add(new MyDispatchAction("Paste"));
MyActionDelegate mydelegate = new MyActionDelegate();
for (int i=0; i<fActions.size(); i++) {
// register mydelegate as
actions.get(i).addActionListener(mydelegate); // handler for all actions
}
}
ArrayList getActions() { return fActions; }
ArrayList fActions;
}
As expected, the menu manager just iterates over the list of actions and associates menu items
with them. Of course, the GUI properties could be taken from a configuration so that the actions
6.5 Java Swing Actions
147
really only act as dispatch objects responsible for forwarding events to delegates which do the
real work.
MyActionProvider creates some dispatch actions that are used by the menu manager. In this
case we attach an instance of MyActionDelegate as listener and actual handler of the action
events; there may also be other actions in other objects that use the same handler. This design
allows sharing of behavior across different classes.
Now, let us deploy these classes in an application:
Program 6.5-10 Using action providers in an application
public class MyDelegateDriver {
public MyDelegateDriver() { }
public static void main(String[] args) {
MyActionProvider actionProvider = new MyActionProvider();
MyMenuManager menuManager = new MyMenuManager(actionProvider);
JFrame myframe = new ...
...
myframe.setJMenuBar(menuManager.getMenuBar());
myframe.pack();
myframe.setVisible(true);
}
}
First, we create an action provider—an object delivering a list of actions. Then, we instantiate a
menu manager, which creates menus and a menu bar and associates the menu items with the
actions delivered by the action provider. Finally, we associate the menu bar with a new frame,
and show the frame including the menu bar. This code fragment shows how to separate GUI
from functional code. The action provider could equally be deployed completely without GUI.
With PCoC, the message dispatch concept is separated from GUI-specific data and behavior.
Dispatchers are responsible for forwarding and delegation (the latter is not supported by Swing
actions) and contain no GUI-specific data and code (see Sections 4.4 and 5.6). Tasks are used
mainly for GUI-specific purposes, and therefore are associated with GUI-specific definitions
from the configuration (see Sections 4.5 and 5.7).
6.5.4 Remarks
The concept of Java Swing Actions, respectively ActionListeners, is powerful and
important. It allows to a certain level the separation of interactive (GUI) and functional part of a
component.
Actions can be used for the following domains:
v Enable or disable a set of controls based on actions.
v Provide descriptions of the action for menus, toolbars, and for the status bar in response to
mouse gestures.
v Forward action events of actions to other objects attached as listeners.
v Abstraction of dispatchers and delegates for forwarding method calls.
Though this concept is very versatile, it also has some limitations. Actions are, as opposed to
PCoC Dispatchers and Tasks, not designed to pass arguments other than action events to their
listeners. Of course, an action could assemble arguments from its environment and forward them
together with a command name in an instance of a new action event class to its listeners.
One problem is also that actions, in contrast to PCoC Dispatchers and Activities, carry some
information for GUI elements. Most literature praises the power of Action to decouple GUI
elements from functional logic. This is only true to a certain level. Actions are designed to be
148
6 Comparison with Related Approaches
attached to menus, or toolbars, otherwise it would not make sense for them to hold a description
string and icon name. Although actions can be used for other things, their support for GUIspecific properties would be an overkill.
Although actions can be composed to groups, they miss some abstraction layers. There is no
general concept for grouping actions.
States are limited to “Enabled” and “Disabled”, whereas the set of PCoC Activity states is not
limited at all.
Actions have no built-in priority management (focus management) that changes the order of
listeners with focus changes in the UI, and the action event may be sent selectively to either only
one receiver or all receivers (broadcast).
Actions cannot be configured to take explicit arguments. Think of menu items and toolbar
buttons for commands such as cut and copy, or one to show context help for a selection in a
text view. They may take the current selection as argument and do something with it. In the case
of actions, each component providing such an action would have to get the current text selection
itself and copy it into the clipboard. In the case of PCoC, one would define the Activities cut,
copy, and showContextHelp so to expect a selection as argument. Any component could
provide selections for them. There would be only one configuration for each of these Activities,
that says that they are coupled with Activity getSelection, no matter by which component(s)
it is provided. Whenever a component is added that supports an Activity getSelection to the
application, the existing Activities cut, copy, showContextHelp would automatically use
this new Activity when the new component has focus (the highest priority). There would not be
the need to add listeners to each type of action that operates on selections.
PCoC aims at more modularity. It is possible to define Activities that deliver objects such as
selections, etc., and to define Activities that need one or many objects as arguments. It is also
possible to combine Activities, respectively Dispatchers, to Tasks. For example, one can
combine Dispatchers Selection getSelection() with copy(Selection) to a Task
copy(getSelection()). In PCoC, such connections are made only by configuration. The
framework automatically registers and unregisters listeners, etc.
A big advantage of Java Swing actions is the simple concept. The concept can be understood
very quickly. It is more difficult to understand the PCoC concepts.
6.6 Method Pointers in C++
The C++ standard ([ISOCPP98], page 83) defines member pointers. This feature is rather
unknown.
Program 6.6-1 Method pointers in C++
class MyClass {
void foo(int i) {};
}
void (MyClass::* x)(int) = &MyClass::foo;
MyClass* o = new MyClass();
o->*x(10);
MyClass o2;
o2.*x(10);
In this case, x is a pointer to a method of MyClass with void as return value and int as
argument. foo can be a static or non-static method. If it is a static method, x is a plain function
pointer. Operators ->* and .* provide access to the member associated with x. In the example,
we pass the integer value 10 as argument to foo.
6.6 Method Pointers in C++
149
As opposed to method objects in Java or C#, which are used for dynamic method invocation, this
construct is fully typed at compile time. With dynamic method invocation, argument types are
checked at runtime (see Sections 6.7.4, 6.7.6, and 6.4.4).
An extended version of this method pointer concept is available in the form of delegates with the
.NET platform (see Section 6.7.5).
For more information about dynamic type information of C++ in the ANSI C++ standard, see
[ISOCPP98] on Pages 71ff, 128, and 341ff, and in [STROU97] on Pages 407ff.
6.7 Microsoft .NET
This section describes reflection and dynamic method dispatch mechanisms of Microsoft’s .NET
architecture, and shows some critical issues for its use in real applications.
6.7.1 Overview
Microsoft introduced the first release candidate of its new technology product .NET at the end of
the year 2001. What Microsoft calls a framework is a combination of a so-called Common
Language Runtime (CLR), class libraries, services, and a set of programming languages and
utilities that support software development.
.NET introduces a new programming language called C# that is said to combine the best of C++
and Java. Other supported languages are Visual Basic, Managed C++ which is an adaptation of
the C++ programming language, and a Java-clone called Visual J++, and a JavaScript-like
programming language called JScript.
The core of a .NET environment is called Common Language Infrastructure (CLI), and
Microsoft’s concrete implementation Common Language Runtime (CLR).
“At runtime, the common language runtime is responsible for managing memory,
starting up and stopping threads and processes, and enforcing security policies, as
well as satisfying any dependencies that one component may have on any other
component.” - [MSNET01] on Page 13.
Basis for this environment is a common standard for object oriented programming languages
(Common Language Specification) and a common type system (Common Type System).
Compilers of .NET do not create machine dependent binaries, but object-code in the Microsoft
intermediate language (MSIL). This code is then translated and optimized by a just-in-time
compiler to be executed natively on a specific target machine. A garbage collector automates
memory management.
The common type system (CTS) provides a programming language-independent concept of data
types, enabling many more programming languages to share data and code on the .NET platform.
In this case, Microsoft learned from their mistakes in the past—for example the difficulties when
sharing data among different programming languages when using COM, DCOM, COM+, etc.
For more information about the CTS, see [MSCTS01] on Microsoft’s .NET Readiness Kit CD.
For Web services, .NET supports protocols such as HTTP, XML, SOAP. SOAP is an XMLbased messaging protocol, which, by the way, will also be supported by PCoC.
.NET also supports security mechanisms for communication between components.
More relevant for this thesis is the improved support for name spaces and name-space
hierarchies. A name space uses a “dot syntax” notation to logically group related classes together.
For example, System.Web.Services conveys that contained classes provide functionality
that is somehow related to Web services. When organizing classes this way it is easy to
understand what functionality they provide. Actually this is the same reason why the concept of
Activity Set containment hierarchy was introduced to PCoC.
150
6 Comparison with Related Approaches
The following sections should provide an overview of reflection facilities supported by .NET and
its programming languages. We will concentrate mostly on C# for simplicity.
Compare .NET reflection to Java reflection facilities in Section 6.4, and especially Section 6.4.4.
6.7.2 Critical issues of .NET
Software companies are often not able to change existing architectures, because of the effort and
risk that have to be invested. Although they may need some of the concepts, they may still be
content with their existing application design. They may only need parts of various frameworks.
So, a crucial question is whether frameworks are modular in design, i.e., if parts of the
frameworks can be deployed independently.
In the case of .NET, the framework facilities can hardly be deployed independently. Developers
who want to use single features have to use the whole architecture which is huge. Specific
programming languages or language extensions (Managed C++, C#, Visual Basic) are required
to make use of the .NET features. We may have the same problem with other architectures or
frameworks, e.g. Java and PCoC. However, PCoC is quite small, and can easily be deployed with
existing (C++ or Java) applications. The framework can be used without GUI extensions (Tasks,
configuration, etc.), in the case that only the delegation facilities (Dispatchers, Activities,
Activity Sets) are required.
On the other hand, a big advantage of .NET is that it supports almost everything that is needed to
create a modern application. It supports interoperability between components written in different
programming languages, it provides a security concept, class libraries (collections, strings, etc.),
and much more.
Another critical issue is the availability of a framework on different platforms. Companies
providing their software for different platforms, cannot use proprietary programming languages.
Reasons are the higher development costs caused by maintenance effort for different source code
on different platforms, extensive developer education, higher effort for integration of a common
architecture for all supported platforms, etc. There are already (open source) projects such as
Mono for Linux to support .NET on other platforms than Microsoft's operating systems. See
[MONO03].
6.7.3 Language Independent Object Model of .NET
COM and COM+ provides only a minimal set of properties of different programming languages.
With COM, it is, for example, not possible to use C++ internal data structures in other
programming languages. This causes a limited interoperability with other programming
languages unless a custom serialization mechanism is put on top of these interface standards. See
also [iX1201], Pages 123f, and 128.
With .NET, Microsoft introduces a language-independent object model. There are value types
and reference types that are stored on the heap. The former are used for basic data types,
enumeration types, etc., the latter for classes, delegates (type-safe function pointers), and
pointers. See [iX1201] on Pages 123f, [WESTP01] on Pages 112f, and [MÖSS02].
6.7.4 Overview of C#
Microsoft introduced a new programming language called C# (C-sharp) with .NET. It combines
some features of C++ and Java and adds some new ones. Microsoft states that C# is the first
component-oriented programming language available. See [MSCSH01] on Microsoft’s .NET
Readiness Kit CD, Page 7, for detailed information.
The most interesting features are, beside the garbage collection provided by the .NET Common
Language Infrastructure for all .NET languages, enumeration types (currently not supported by
Java; will be introduced in Java 1.5), properties (value assignment of member variables as setter
6.7 Microsoft .NET
151
and getter methods; see Section 6.7.8) and indexers (special case of properties; see Section
6.7.8), attributes (meta information that can be attached to program elements such as classes,
methods, etc.; see Section ), and delegates. A concept called boxing allows to keep primitive data
types such as int compatible to Object.
The reflection mechanism supports retrieving and using types, methods, fields, properties, and
others at runtime. See also [MICROS01] on Microsoft’s .NET Readiness Kit.
Methods are no first class objects in C#, or in .NET in general. When method pointers and
dynamic invocation is required, one can use delegates, or .NET reflection facilities for dynamic
invocation (GetMethod).
The following sections offer an insight into some .NET and, mainly, C# capabilities.
There is a good comparison of C#, Java, and C++ in [BREYM02] (in German) on Pages 98ff; a
comparison table is on Page 105.
6.7.5 .NET Delegates
6.7.5.1 Overview
With the .NET platform, Microsoft has introduced a powerful concept of type-safe method
pointers called delegates. A .NET delegate represents a class derived from
MulticastDelegate rather than a raw memory address. The main differences to C++
method pointers are:
v A delegate stores the target object (the receiver) if associated with a non-static method.
However, delegates can also be associated with static methods. In this case, the target object
is not set.
v A delegate can be associated with more than one method, for so-called multicasts.
It is quite common that objects engage in two-way conversation. To cope with threading issues,
callback methods are frequently used. In object-oriented languages these must also be able to
point to non-static methods (object methods). This is where delegates are useful. Although the
C++ standard already supports method pointers to non-static methods (see Section 6.6), .NET
delegates are easier to use and more extensible.
In contrast to method objects provided by the reflection mechanisms of Java and .NET, delegates
are first class objects. The advantage of .NET delegates compared to dynamic method calls using
reflection, and to PCoC, is their type-safety at compile-time. This helps to find wrong argument
types earlier than with dynamic method invocation.
PCoC Dispatchers are basically similar to delegates (they also dispatch requests to methods and
support multicasts) and to method objects provided by the Java and .NET reflection (they are
invoked dynamically; argument types are checked at runtime). However, as opposed to delegates,
Dispatchers support delegation, and not only forwarding.
Activities and their Dispatchers can be assembled at runtime, and can be added and removed at
any time. So-called remote Activities can be attached to an Application using XMLRPC (and
also RMI when using Java).
With .NET it is also possible to define (or better assemble) behavior at runtime. Assemblies,
classes and methods can be defined completely at runtime using the
System.Reflection.Emit name space. See [LIBERT01], Chapter 18. PCoC is not that
powerful (the implementation of Activities can only be defined at runtime if they are attached via
a remote connection), but this makes, on the other hand, the framework easier to use.
152
6 Comparison with Related Approaches
The capability for adding Activities or methods at runtime is useful if some functionality of a
component should be loaded, or better extended via a remote connection or through
configuration at runtime.
Delegates were first introduced in Microsoft’s Visual J++, also designed by Anders Hejlsberg,
and were a cause of much technical and legal dispute between Sun and Microsoft.
Sun states in [SUNDLG99] that bound method references such as delegates add complexity, the
concept results in loss of object-orientation, delegates are limited in expressiveness—they are
allegedly nothing more than function pointers (which is not true; see the description of delegates
above), and they are no more convenient than adapter objects. Note that these comments do not
reflect the opinion of the author. With these results in hand, the designers of the Java
programming language decided to introduce inner classes, which can be used for purposes where
usually delegates are used in .NET, for example, for call-backs, etc.
Sun also states in this article that any implementation of bound method references would either
be inefficient or non-portable. A portable implementation of delegates that generates code
capable of running on any compliant implementation of the Java platform would have to rely on
the standard Java Core Reflection API.
Microsoft states in [MSDLG99] that delegates are simpler and more efficient and portable than
inner classes. They also say that delegates are better for multicasting, and reduce the number of
classes drastically, which leads to smaller binaries.
For more information, see Sun’s original article [SUNDLG99], and Microsoft’s response
[MSDLG99].
6.7.5.2 Using Delegates
Delegates are declared using the delegate (C#, Visual Basic) or __delegate (Managed
C++) keyword.
Let us compare delegates to Java action listeners in the following example.
Program 6.7-1 Using an ActionListener in Java
public class MyClass implements ActionListener {
public MyClass() {
button = new JButton("Test");
button.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
...
}
}
In the constructor of MyClass, we create a new JButton instance and add this as action
listener. JButton holds a list of action listeners and notifies each, when the button is clicked.
More precisely, it calls the method actionPerformed. As action listener, MyClass must
implement the ActionListener interface, including the actionPerformed method.
6.7 Microsoft .NET
153
Here a semantically equivalent implementation using delegates:
Program 6.7-2 Declaring a delegate in C#
public delegate void ActionListener(ActionEvent e);
public class Button {
public ActionListener click;
...
}
public class MyClass {
public MyClass() {
button = new Button();
button.click += new ActionListener(ActionPerformed);
}
public void ActionPerformed(ActionEvent e) { ... }
}
In this C# code fragment, we declare a delegate ActionListener. In the Button class, we
use this delegate to hold and notify listeners of button clicks (in contrast to JButton in Program
6.7-1, where we use a list of action listeners). In the constructor of MyClass, we add a new
ActionListener delegate which is associated with the method ActionPerformed. Note
that the return type and parameter types of the delegate and the associated method must match.
With the Java action listener concept, we can add and remove listeners (listener objects)
arbitrarily. All listeners must implement the ActionListener interface, including the method
actionPerformed. With .NET, all methods which match a delegate's return type and
parameter types can be added to and removed from the delegate.
Let us assume that we want to implement a logging facility that takes a message id and a string
message as parameter:
Program 6.7-3 Declaring a delegate in C#
// This delegate actually represents a class encapsulating a function pointer to
// some method taking an integer and string argument and returning void.
public delegate void Logger(int id, string msg);
Such a delegate can be declared inside of a class or stand-alone. The given delegate actually
represents a class encapsulating a function pointer plus a receiver to some method taking an
integer and string argument and returning void.
Note that since beta 2 of .NET, all delegates are multicast delegates, therefore able to send events
to one or many targets.
A compiler implicitly generates a delegate class for each delegate declaration. These classes are
always derived from System.MulticastDelegate (C#, Visual Basic), respectively
System::MulticastDelegate (Managed C++), and accordingly in other programming
languages.
The C# compiler produces the following class from the declaration above:
Program 6.7-4 A delegate class in C#
public class Logger : System.MulticastDelegate {
Logger(object target, int ptr);
// The synchronous Invoke method
public virtual void Invoke(int id, string msg);
// The asynchronous version
public virtual IAsyncResult BeginInvoke(int id, string msg,
AsyncCallback cb, object o);
public virtual void EndInvoke(IAsyncResult result);
}
154
6 Comparison with Related Approaches
This class is only created implicitly by the compiler; this is not source code.
Now that we have declared the delegate, we can make use of it. The following class, respectively
its method, writes messages to the console:
Program 6.7-5 A console logger in C#
public class ConsoleLogger {
public ConsoleLogger() { }
void PrintMessage(int id, string msg) {
Console.WriteLine("Message {0}: {1}", id, msg);
}
}
Note that the PrintMessage method has the same parameter and result types as the delegate.
The following logger writes to a file:
Program 6.7-6 A file logger in C#
public class FileLogger {
public FileLogger(string logfilepath) {
FileInfo f = new FileInfo(logfilepath);
f.Open(FileMode.OpenOrCreate,
FileAccess.ReadWrite, FileShare.None);
sw = f.AppendText();
}
~FileLogger() { sw.Close(); }
void LogMessage(int id, string msg) {
sw.WriteLine("Message {0}: {1}", id, msg);
sw.Flush();
}
private string logfilepath;
private StreamWriter sw;
}
Note that we are intentionally using another method name (LogMessage) to demonstrate that
only parameter and result types are relevant to delegates.
Now we implement a driver class, our application entry point:
Program 6.7-7 Our main class in C#
public class MyApp {
public static int Main(string[] args) {
ConsoleLogger cl = new ConsoleLogger();
Logger logger = new Logger(cl.PrintMessage);
logger(100, "Sample Message");
// logs to console
}
}
In Program 6.7-7, we create a logger delegate from the PrintMessage method of a
ConsoleLogger instance. When we invoke the logger, the call is actually forwarded to
cl.PrintMessage(100, "Sample Message").
For static methods, the target is not specified (cf. object cl is in the example above).
Program 6.7-8 A static method as callback
public static void StaticLog(int id, string msg) { ... }
Logger logger = new Logger(StaticLog);
6.7 Microsoft .NET
155
Program 6.7-8 shows nothing but the use of a delegate as an ordinary function pointer. However,
delegates can also be useful for other things such as multicasting to different methods, which is
not possible with C(++) function pointers.
Compare delegates to C++ method pointers in Section 6.6.
Now back to our example. If we want to log to the console and to a file, we can simply do the
following:
Program 6.7-9 Our main class in C#
public class MyApp {
public static int Main(string[] args) {
FileLogger fl = new FileLogger("c:/test.log");
ConsoleLogger cl = new ConsoleLogger();
Logger consoleLogger = new Logger(cl.PrintMessage); // logs to console
Logger fileLogger = new Logger(fl.LogMessage);
// logs to a file
Logger logger = (Logger)consoleLogger+fileLogger;
logger(100, "Sample Message");
// logs to console AND a file
}
}
In Program 6.7-9, respectively Program 6.7-30, the methods of the console logger and the file
logger are called accordingly. To log to the console only, we can remove the file logger again:
Program 6.7-10 Removing a delegate
Logger logger = (Logger)consoleLogger+fileLogger;
logger(100, "Sample Message");
// logs to console AND a file
logger -= fileLogger;
logger(100, "Sample Message2");
// logs to console only
6.7.5.3 Remarks
The delegate concept was introduced as part of Microsoft’s .NET platform. Delegates are method
pointers that are more powerful than function pointers of C and C++. Each .NET programming
language supports this concept.
One important feature of .NET delegates is the ability to use non-static methods as callbacks.
The C++ standard already supports method pointers to non-static methods, but delegates are
easier to use. Another important feature of delegates is the ability to combine methods to
multicasts—if such a delegate is invoked, it forwards calls to all attached methods. The
advantage of delegates as opposed to dynamic method calls using the Java or .NET reflection is
their type safety at compile time.
In contrast to method objects provided by the reflection mechanisms of Java and .NET, delegates
are first class objects. The advantage of .NET delegates compared to dynamic method calls using
reflection, and to PCoC, is their type-safety at compile-time. This helps to find wrong argument
types earlier than with dynamic method invocation.
PCoC Dispatchers are basically similar to delegates (they also dispatch requests to methods and
support multicasts). However, as opposed to delegates, Dispatchers support delegation, and not
only forwarding.
When invoking a Dispatcher one specifies the Activity Set, the name of the method and an array
of arguments. Dispatchers automatically combine methods of the same name and signature, as
opposed to delegates, which can be used to explicitly combine methods with a specific parameter
and result types signature. Both can be used to make single or multicasts (broadcast to attached
methods). Both allow attaching and detaching callback methods at runtime. In the case of PCoC
156
6 Comparison with Related Approaches
the callbacks are again Dispatchers or Activities (operations that actually perform requests). See
also Section 4.4.
For a good and detailed description of C# delegates, see [TROEL01] on Pages 250ff, and
[LIBERT01] on Pages 277ff. The latter describes delegates also for Managed C++ and Visual
Basic.
6.7.6 .NET Late Binding and Dynamic Method Invocation
6.7.6.1 Overview
Like Java, .NET supports reflection facilities such as dynamic exploration of types and dynamic
method invocation. Late binding is a mechanism that allows resolution of the existence and name
of a given type and its members at runtime rather than at compile time. Once the presence of a
type has been determined, we can dynamically invoke methods, access properties, and
manipulate the fields of a given entity.
Read about dynamic method invocation of Java in Section 6.4.4.
6.7.6.2 Dynamic Method Invocation
The following example illustrates dynamic method invocation in .NET. We use the
System.Activator class to instantiate the class MyClass, which we have put into an
assembly MyAssembly.
Program 6.7-11 Class instantiation in .NET
public class MyApp {
public static int Main(string[] args) {
// Load MyAssembly
Assembly a = null;
try { a = Assembly.Load("MyAssembly"); }
catch(FileNotFoundException e) { Console.WriteLine(e.Message); }
// Get MyClass
Type cl = a.GetType("MyAssembly.MyClass");
// Create an instance of MyClass
object o = Activator.CreateInstance(cl);
...
}
}
Now, cl points to a class MyClass loaded from assembly MyAssembly. o points to a new
instance of this class.
Now, let us take a look at the implementation of MyClass:
Program 6.7-12 Class MyClass
public class MyClass {
public void foo() { Console.WriteLine("Hello World!"); }
}
For simplicity of this example, this class contains only one method without any parameters.
Next we retrieve a MethodInfo object for method foo, which we can then invoke by using
the MethodInfo.Invoke method.
6.7 Microsoft .NET
157
Program 6.7-13 Dynamic method invocation in .NET
public class MyApp {
public static int Main(string[] args) {
...
// Get MethodInfo
MethodInfo mi = cl.GetMethod("foo");
// Invoke method without arguments
mi.Invoke(o, null);
return 0;
}
}
Let us assume that MyClass also has a method that takes a string and an integer parameter.
Program 6.7-14 Class MyClass (2)
public class MyClass {
public void foo() { Console.WriteLine("Hello World!"); }
public void printMessage(int id, string msg) {
Console.WriteLine("Message {0}: {1}", id, msg);
}
}
The invocation procedure looks a little more complicated:
Program 6.7-15 Dynamic method invocation with arguments in .NET
// Get MethodInfo
MethodInfo mi = cl.GetMethod("printMessage");
object[] arguments = new object[2];
arguments[0] = 100;
arguments[1] = "A Message";
// Invoke method with arguments
mi.Invoke(o, arguments);
If the invoked method returns a value, it has to be casted to the correct type.
To clearly determine the right method, we may want to specify the full signature of the method to
be invoked:
Program 6.7-16 Dynamic method lookup with parameter types
// Get MethodInfo
Type[] parameterTypes = new Type[2];
parameterTypes[0] = Type.GetType("System.Integer");
parameterTypes[1] = Type.GetType("System.string");
MethodInfo mi = cl.GetMethod("printMessage", parameterTypes);
The similarities to Java should be apparent (cf. Section 6.4.4 about dynamic method invocation
in Java).
6.7.6.3 Remarks
Late binding and dynamic method invocation is mostly needed for component interoperability
and dynamic (re-)use. Almost every modern programming language such as Java and the .NET
platform languages support reflection, which is necessary for dynamic method invocation.
We remember that PCoC Activities are method objects and include references to their providers.
Activities can be added to/removed from a component all at runtime. Dispatchers are similar to
method pointers and delegates, and delegate requests to Activities. When invoking a Dispatcher,
we specify the Activity Set, the name of the method and an array of arguments. See also Section
4.4. The PCoC constructs are not type safe at compile time, but at runtime.
158
6 Comparison with Related Approaches
6.7.7 Name Spaces on .NET
6.7.7.1 Overview
When developing applications, it may be useful to group semantically related types into custom
name spaces. For this reason, each .NET platform language supports name spaces, and Java
supports them with the package concept.
In C#, name spaces can be defined using the namespace keyword.
6.7.7.2 Using Name Spaces
The following example corresponds to the example in [TROEL01] on Pages 23-24.
Program 6.7-17 Using name spaces in .NET
// Hello world in C#
using System;
public class MyApp {
public static void Main() { Console.WriteLine("Hello World!"); }
}
In this case, we use name space System where the Console class is defined. There are only
slight syntactical differences between the various .NET programming languages, which is elegant
for developers using .NET.
Program 6.7-18 A stack implementation
using System;
using System.Collections;
namespace OriginalNamespace {
public class MyStack {
private Stack stack;
public MyStack() { stack = new Stack(); }
public void Push(int i) {
if (i >= 0) { stack.Push(i); }
}
public int Pop() {
if (stack.Count > 0) { return stack.Pop(); }
else { return -1; }
}
public int Peek() {
if (stack.Count > 0) { return stack.Peek(); }
else { return -1; }
}
public int Size() { return stack.Count(); }
}
}
Name spaces are a way to group semantically related types such as classes, enumerations,
interfaces, delegates, and structures. They are also useful to distinguish different implementations
of syntactically equal types.
The stack implementation of Program 6.7-18 allows adding of positive numbers; we use the
Stack class of the name space System.Collections.
Now we may want to add trace capabilities to this class, but only for debug reasons. In the
release version of our application we may not want such time-consuming checks. In the
following code fragment, we do not change the original implementation, but rather override it in
another name space:
6.7 Microsoft .NET
159
Program 6.7-19 Added tracing capability for the original stack implementation
namespace MyDebugNamespace {
class MyStack : public OriginalNamespace.MyStack {
private Stack stack;
public void Push(int value) {
int prevsize = Size();
Console.WriteLine("MyStack.Push: {0}", value);
base.Push(value);
if (Size() != prevsize+1) {
Console.WriteLine("Error in MyStack.Push: value not added");
}
else if (base.Peek() != value) {
Console.WriteLine("Error in MyStack.Push: wrong last element");
}
}
public int Pop() {
int value = 0;
if (Size() > 0) {
int prevsize = Size();
value = base.Pop();
Console.WriteLine("MyStack.Pop: {0}", value);
if (Size() != prevsize-1) { Console.WriteLine(
"Error in MyStack.Pop: value not removed correctly"); }
}
else {
Console.WriteLine("Error in MyStack.Pop: stack is empty");
}
return value;
}
}
}
Now we have a stack class that overrides the original class and adds some tracing. Our client
code might look like the following:
Program 6.7-20 Using name spaces
using OriginalNamespace;
// Original MyStack without tracing
// using MyDebugNamespace; // MyStack with tracing
MyStack stack = new MyStack();
stack.Push(10); // push an integer value into the stack
Depending on the name space we use, either the original implementation of MyStack is taken,
or that with tracing capabilities. The client code can stay the same in any case. During the
implementation and test phase of source code we may use the name space with added tracing,
and for the release version of the application, the original name space.
Note that Java supports a package concept. A Java package is a named scope and can be
imported by using the import keyword. For example, import java.lang.*.
Name spaces are used often for aspect-oriented programming, where they can help to simplify
the replacement of implementations, or the sharing of aspects among many classes. See also
Section 6.2.
6.7.7.3 Name-Space Aliases
A cleaner and more convenient approach to resolve name space ambiguity are name-space
aliases. For example, we can use them instead of the name-space type definitions of Program 6.720:
160
6 Comparison with Related Approaches
Program 6.7-21 Name-space aliases in C#
// A name-space alias
using MyStack = MyDebugNamespace.MyStack; // or: using MyDebugNamespace
MyStack stack = new MyStack();
stack->Push(10); // push an integer value into the stack
The following code fragment illustrates the support of aliases in PCoC:
Program 6.7-22 Name-space aliases in C#
PCCActivitySet a = PCCRegistry.getOrCreateActivitySet("A");
PCCRegistry.setAlias("MyStack", "A");
// or: a.setAlias("MyStack")
a = PCCRegistry.getActivitySet("MyStack"); // returns Activity Set "A"
6.7.7.4 Nested Name Spaces
Like Java packages, .NET name spaces can be nested.
Program 6.7-23 Nested name spaces in C#
namespace MyApp {
namespace MyServices {
class Assert { ...
}
class Tracing { ...
}
}
namespace MyWidgets {
class TableView { ...
}
class GraphView { ...
}
}
}
Now, the name spaces can be imported selectively:
Program 6.7-24 Name-space aliases in C#
using MyApp.MyServices;
using MyApp.MyWidgets;
Almost every modern programming language supports nested name spaces. In PCoC, we use
Activity Sets. They are named scopes like name spaces, but can be created at runtime. See also
Sections 3.3 and 5.8.
6.7.7.5 Remarks
The concept of name spaces or packages, including nesting, is supported by almost all modern
object-oriented programming languages. Name spaces allow grouping of different, mostly
semantically related types. They can also be used to vary aspects of source code simply by using
the one or other name space defining the same types, but with different implementations.
In short, name spaces can help to increase modularity, reusability, and to replace
implementations of aspects by others without affecting client code.
We may encounter situations where these facilities are needed even at runtime. The name spaces
supported by .NET are static. Once a .NET program is compiled and executed, it is no longer
possible to reorganize its name spaces.
6.7 Microsoft .NET
161
With PCoC Activity Sets, the features of name spaces can be achieved at runtime.
See also Sections 3.3, 5.3, 5.4.3, and 5.8.
6.7.8 .NET Properties
Properties are a new concept introduced with C#. A property is not a regular data field of a class,
but a method pair set/get that just looks like a field (see Program 6.7-25). It usually hides and
encapsulates data fields. Properties are said to be a natural extension of data fields, and are
known as smart fields in the C# community.
In the following example, we define the properties x and y:
Program 6.7-25 A C# property
public struct Point {
double fx;
double fy;
public double x {
set {
fx = value;
}
get {
return fx;
}
}
public double y {
set {
fy = value;
}
get {
return fy;
}
}
}
We can then access the properties as follows:
Program 6.7-26 Accessing a C# property
Point q = new Point();
q.x = 20; // set-method is called implicitly
Console.WriteLine(q.x); // get-method is called implicitly
q.x can be accessed like a data field, but actually the corresponding set and get methods are
called. Usually there may be real data fields behind properties, like fx in Program 6.7-25.
Indexers are a special case of properties. They allow indexed access to user defined classes.
Program 6.7-27 C# indexer
public enum CoordinateDimension : short { X = 1, Y = 2 }
public double this[CoordinateDimension which] {
set {
if (which == CoordinateDimension.X) fx = value;
else fy = value;
}
get {
if (which == CoordinateDimension.X) return fx;
else return fy;
}
}
162
6 Comparison with Related Approaches
Program 6.7-27 shows the implementation of an indexer in the class Point of Program 6.7-25.
The indexer is specified by using the keyword this. The parameter and return types can be
selected arbitrary.
This concept is not available in Java. In C++ the same effect can be achieved by overloading the
index-operator [].
The following example shows the use of an indexer:
Program 6.7-28 C# indexer usage
Point q = new Point();
q[CoordinateDimension.X] = 20;
q[CoordinateDimension.Y] = 30;
The members are accessed by using indexes. The set and get methods are called implicitly.
See also [TROEL01] on Pages 147-149.
6.7.9 Remarks
The reflection facilities described in this chapter are only a small excerpt of what .NET actually
provides. However, all facilities relevant for this thesis were explained, or at least mentioned, to
get a rough overview what is possible with .NET. Another rarely described feature supported by
.NET is Expando, an interface that allows to add and remove members at runtime:
Program 6.7-29 Expando (excerpt)
// System.Runtime.InteropServices.Expando:
public FieldInfo AddField(String name);
public MethodInfo AddMethod(String name, Delegate method);
public PropertyInfo AddProperty(String name);
public void RemoveMember(MemberInfo m);
The System.Reflection.Emit allows more dynamic things such as defining assemblies,
modules, types, and methods completely at runtime.
The following code fragment illustrates a possible use of the System.Reflection.Emit
name space.
Program 6.7-30 Dynamically creating a class and a method (C#) (1)
using System;
using System.Reflection;
using System.Reflection.Emit;
public class TestFoo
{
Type t;
public TestFoo() {
MakeFoo();
UseFoo();
}
UseFoo()
{
object o = Activator.CreateInstance(t);
t.GetMethod("Foo").Invoke(o, null);
}
...
6.7 Microsoft .NET
163
Program 6.7-31 Dynamically creating a class and a method (C#) (2)
...
MakeFoo()
{
// Create a dynamic assembly in the current domain
AssemblyName assemblyName = new AssemblyName();
assemblyName.Name = "FooAssembly";
AssemblyBuilder fooAssembly = Thread.GetDomain().DefineDynamicAssembly(
fooAssembly, AssemblyBuilderAccess.Run);
// Create a module in the assembly and a type in the module
ModuleBuilder fooBuilder = fooAssembly.DefineDynamicModule("fooModule");
TypeBuilder fooType = fooBuilder.DefineType("FooType",
TypeAttributes.Public);
// Add a Foo method to the type
MethodBuilder fooMethod = fooType.DefineMethod("Foo",
MethodAttributes.Public,
null, null);
// Generate the implementation for "Foo"
ILGenerator ilg = fooMethod.GetILGenerator();
ilg.EmitWriteLine("Hello World!");
ilg.Emit(OpCodes.Ret);
// Finalize the type so we can create it
fooType.CreateType();
// Create an instance of the new type
t = Type.GetType("FooType");
}
}
In the constructor, we first call the method MakeFoo which creates an assembly, a module, a
type, and a method dynamically. We define the new method “Foo” to write “Hello World!”
to the standard output. In UseFoo, we dynamically invoke the newly created method.
.NET provides powerful reflection facilities, equal or even surpassing those of Java. However,
issues for its deployment may be the Microsoft licensing model, and whether it will be available
on operating systems other than the Microsoft Windows family. In any case, the concepts are
useful for modularity and reusability of software components.
More details about .NET attributes can be found in [TROEL01] on Pages 373ff, and
[LIBERT01] on Pages 457ff.
As opposed to .NET delegates and name spaces, the PCoC approach is a purely dynamic one. As
mentioned earlier in this thesis, the framework introduces dynamic facilities such as Activity Sets
and the ability to add and remove operations at runtime. This allows to dynamically organize
objects and components in groups and prioritized acquisition relationships, to apply different
aspects to objects, to switch Activity Sets, and to modify objects (adding and removing
members). These features may not always be needed, but they are useful for reusability and
interoperability of objects or components.
PCoC does not provide support for fields, but only for operations, more precisely Activities.
Fields must be accessed through operations. Note that even .NET properties encapsulate field
access.
Compare .NET reflection to Java reflection facilities in Section 6.4, and especially Section 6.4.4.
6.8 Smalltalk
Smalltalk is a purely object-oriented programming language based on classes. It has affected or
was even the basis for the design of many modern programming languages such as, for example,
Java.
164
6 Comparison with Related Approaches
In Smalltalk, even primitive data types and methods are objects—so-called first class objects.
Classes are also objects. All Smalltalk processing is accomplished by sending messages, like in
Self (cf. Section 6.10).
Objects are instances of classes. The messages that an object can respond to are defined in the
interface of its class. Methods define how messages are executed and represent a class behavior.
Sending a message to an object causes the method search to begin with the receiving object
itself. If the method is not found in the class of the object, the method is searched for up the class
hierarchy. The same is also valid for sending messages from within an object to itself using the
identifier self.
See [FOOTE89], [GOLDB83], [IBMST95], and [GITTI00] for descriptions of the Smalltalk
language and its concepts.
With PCoC, operations are exposed as Activities. They are treated as first class objects like
methods in Smalltalk. With Dispatchers, PCoC offers a message dispatch mechanism that
supports delegation. When a Dispatcher is invoked, it searches its acquisition parents for one or
many Activities (like in Smalltalk where methods are searched in an object's class and then in its
superclasses), gathers them in a distinct list and finally invokes them. As opposed to PCoC,
Smalltalk does not support multicasts for the message dispatch. The structure of object sets can
be modified at runtime by adding or removing Activities. In Smalltalk it is possible to add,
remove and wrap methods at runtime. See Chapter 2 for a detailed comparison of the message
dispatch mechanisms of Smalltalk and PCoC. Section 5.6 describes PCoC Dispatchers in more
detail.
6.9 Oberon Message Objects
This section provides an overview of the message object concept proposed with Oberon-2, and
how this relates to similar concepts in PCoC.
6.9.1 Overview
“Oberon-2 is a general-purpose programming language in the tradition of Pascal and
Modula-2 and was developed at the ETH Zurich. Its most important features are
block structure, modularity, separate compilation, static typing with strong type
checking (also across module boundaries), and type extension with type-bound
procedures.
Type extension makes Oberon-2 an object-oriented language. An object is a variable
of an abstract data type consisting of private data (its state) and procedures that
operate on this data. Abstract data types are declared as extensible records. Oberon-2
covers most terms of object-oriented languages by the established vocabulary of
imperative languages in order to minimize the number of notions for similar
concepts.” - [MÖSS91].
See Section 2.2.3 for an overview of Oberon message objects and interpreters.
6.9.2 Remarks
Message objects have some advantages over methods ([MÖSS98]):
v Messages are data packages; they can be stored and forwarded later on.
v A message object can be passed to a method (the message interpreter) that forwards it to
different objects. This allows broadcasts, which are not or difficult to realize with methods.
v It is sometimes easier if the sender does not have to care about whether a receiver
understands a message (implements a corresponding method) or not.
6.9 Oberon Message Objects
165
v It is also possible to access the message interpreter (handler) through a method reference,
which enables to replace it by another at runtime.
Message objects also have disadvantages:
v The interface of a class does not reflect which messages can be handled by instances of the
class. It is difficult to realize at compile time which objects are related through message
forwarding at runtime.
v Messages are interpreted and forwarded at runtime, which is much slower than direct
method calls. It depends on how fast dynamic type information is evaluated, or in the case
of handling messages using their names, how fast strings are parsed.
v Sending message objects requires more code to be written. Arguments must be packed in
the message object. A regular interface such as for methods is not available.
v Invalid messages are not recognized at compile time. Although this provides the flexibility
to forward messages through objects that cannot handle them themselves, it can be
troublesome to find errors.
Generally, one should use methods rather than message objects. However, in some cases it is
really useful to use message objects.
PCoC also sends message objects, or kind of. Messages in PCoC consist of the following parts:
the message path including the name of the Dispatcher that should be used, where the Dispatcher
name corresponds to the name of the message that should be forwarded; some directives for
delegating the message (for example, broadcast or single cast); an argument container (a Material
instance); the method (perform, getState, etc.) which should be invoked on the
corresponding Activities. Objects can register for specific messages. To do this, it is necessary to
specify name and dynamic signature (parameter and return value types) of the message. For
example, for a message with signature void select(int from, int to), we would
write:
Program 6.9-1 A PCoC Activity in Java
addActivity(new PCCSingleActivity("select", "SelectionCategory", "", "int,int") {
...
}
Note that this statement registers the receiver for messages with the name select. It does not
represent an actual message object. The receiver itself is an operation implemented as an
anonymous class derived from PCCSingleActivity. Whenever a message with name
select is delegated to the Activity Sets of the object that registered the Activity, the Activity
will be invoked, which itself can delegate the message or simply call a method of the surrounding
object.
The interface definition ensures that the argument lists of all select messages sent to the
current object will have the given interface; the invocation of Activities is type-safe at runtime.
PCoC Dispatchers act as what we called “handler” or “message interpreter” earlier in this
section.
Receiver operations are also objects, because this provides the flexibility not only to handle
messages at runtime, but also to extend receiver objects with operations at runtime. The state of
receivers can also be handled with PCoC. Possible states are “Enabled” or “Disabled”, and any
other strings. Implementations such as license management for operations can be encapsulated or
at least simplified by this approach. With the PCoC architecture, it is possible to let objects
dynamically acquire operations, or better, corresponding Dispatchers, from other objects. This
improves code and object reuse and sharing.
166
6 Comparison with Related Approaches
Generally, all the advantages and disadvantages mentioned here for message objects are also
valid for PCoC.
An introduction into message objects is given in Section 2.2.3. Read more about PCoC in
Chapters 4 and 5, especially Sections 5.4 and 5.5. Information and references to Oberon-2 are
available in [MÖSS91], [MÖSS98], [WIRTH92].
6.10 Self
One of the most closely related sources of this thesis is a former feature of the programming
language Self: prioritized multiple inheritance. The feature was removed in version 3.0. This
section gives an overview of this programming language, and how it is related to this thesis.
6.10.1 Overview
“Self is an object-oriented language for exploratory programming based on a small
number of simple and concrete ideas: prototypes, slots, and behavior. Prototypes
combine inheritance and instantiation to provide a framework that is simpler and
more flexible than most object-oriented languages. Slots unite variables and
procedures into a single construct. This permits the inheritance hierarchy to take over
the function of lexical scoping in conventional languages. Finally, because Self does
not distinguish state from behavior, it narrows the gaps between ordinary objects,
procedures, and closures. Self’s simplicity and expressiveness offer new insights into
object-oriented computation.” - [UNGAR87].
Unlike other object-oriented programming languages, Self supports neither classes, nor variables.
Instead, Self has adopted a prototype metaphor for object creation. Furthermore, while other
object-oriented programming languages support variable access as well as message passing, Self
objects access their state information solely by sending messages to “self”. The programming
language is named after this messaging concept. Due to this approach, message passing is more
fundamental in Self than in other programming languages. Methods and fields are stored in socalled slots. Methods are objects associated with code, as opposed to fields which contain data.
Normally slots are readable, no matter if accessing data or methods. If there is a corresponding
assignment slot (for example, “x:”) for a data slot (“x”), the member variable (“x”) is
considered read-write. Microsoft lately introduced a similar concept called properties with its
new platform .NET (about 15 years after Self showed up the first time). Read more about .NET
properties in Section 6.7.8.
6.10.2 Prototyping vs. Class Inheritance
[UNGAR87] describes on Page 3 the differences between class-based programming languages
and Self as follows:
facility
inheritance relationships
class–based systems
instance of
subclass of
Self: no classes
inherits from
creation metaphor
build according to plan
clone an object
initialization
executing a “plan”
cloning an example
one-of-a-kind
need extra object for the class
no extra object needed
infinite regress
class of class of class of ...
none required
Creating new objects from prototypes is accomplished by a simple operation, copying. This
approach has a simple biological metaphor, cloning. Creating new objects from classes as in
6.10 Self
167
class-based programming languages is accomplished by instantiation, which includes the
interpretation of format information in a class. Instantiation is similar to building a house from a
plan. Copying is often perceived to be a simpler metaphor than instantiation, since it is known
from processes in nature.
Self allows multiple inheritance, which requires a strategy for dealing with multiple parents. It
has block closure objects, and threads.
In prototype languages the structure of objects is not defined separately in classes. Existing
objects can be copied and and extended and/or modified.
Let us take a look at the following example which illustrates differences to class-based
approaches:
4
3
print
...
move(x,y)
...
parent*
x
10
y
10
x:
y:
parent*
1
x
parent*
20
2
x
y
y
y
15
x:
x:
y:
y:
Figure 6.10-1 Self objects
Here we have some prototypical objects. These Self objects describe point structures. Each of
them describes its own format, but some refer to members of other objects.
Object 1 is a copy of parent 3 and redefines slot (field) x. Slot y stays the same as in object 3.
Object 2 is also a copy of 3 and redefines both slots. Object 3 is a copy of 4, thus it also has
methods move and print, so do objects 1 and 2. Each object has a pointer to its parent, except
object 4, which fully describes its own format.
Note that objects can have more than one parent object—Self supports multiple inheritance
(through delegation).
Slots of Self objects can either be readable, or assignment slots. For example, x, y, print, and
move of the example above are considered to be readable slots. x: and y: are assignment slots
for assigning values to x and y. The ← represents the assignment primitive operation, which is
invoked to modify the contents of corresponding data slots. Note that values x and y are
accessed through messages only, as well as print and move, which are considered as methods.
Methods are Self objects that include code. Note that in other programming languages, such as
Smalltalk and Python, methods are also first-class objects like in Self (cf. Section 6.8 and
[ROSSUM90]).
168
6 Comparison with Related Approaches
The object in the print slot above includes code and thus serves as a method. However in Self,
any object can be regarded as a method. A “data” object contains code that merely returns itself.
See also [UNGAR87] and [SMITH95].
If accessing slots in a prototypical object, and the object itself does not define these slots, then
the parent object is searched, and then slots in the parent’s parent, and so on.
Generally, it is much easier to use templates such as prototypes and modify them, instead of
creating something new from a plan. Think of planning and building a house—doing everything
yourself starting with a basic plan is a lot of work and needs a lot of understanding. Selecting an
existing and appropriate plan, letting someone build a house according to that, and finally
making own modifications is easier and faster.
PCoC provides proper base classes and configurations which should simplify component
development. They can be considered as templates for concrete components.
6.10.3 Prioritized Multiple Inheritance
6.10.3.1 Overview
Prototypical programming languages do not include classes, but instead allow individual objects
to delegate to (dynamically inherit from) other objects. [CHAMB91] describes the inheritance
and encapsulation mechanisms designed and implemented in early versions of Self. The
inheritance system of Self supports a unique sender-path tie-breaker rule that resolves many
ambiguities between unrelated unordered (not ranked) parents in multiple inheritance
relationships, and dynamic inheritance which allows an object to change its parents at runtime to
effect significant behavioral changes due to changes in its state.
Earlier versions of Self (before version 3.0) supported prioritized multiple inheritance. Randall
B. Smith explains in [SMITH95] on Page 6 that it was a mistake introducing prioritized multiple
inheritance into Self:
“It is always tempting to add a new feature that handles some example better.
Although the feature had made it possible to directly handle some examples, the
burden it imposed in all reasoning about programs was just too much. We abandoned
it for Self 3.0. Although adding features seems good, every new concept burdens
every programmer who comes into contact with the language”.
Note that Self still supports multiple inheritance, but parents are not prioritized any more.
The reason for introducing this feature in an early version is explained as follows:
“We prioritized multiple parent slots in order to support a mix-in style of
programming. The sender-path tie-breaker rule allows two disjoint objects to be used
as parents, for example a rectangle parent and a tree node parent for a VLSI cell
object. The method-holder-based privacy semantics allowed objects with the same
parents to be part of the same encapsulation domain, thereby supporting binary
operations in a way that Smalltalk cannot”.
The statement continues:
“But each feature also caused us no end of confusion. The prioritization of multiple
parents implied that Self’s resend (call-next-method) lookup had to be prepared to
backup down parent links in order to follow lower-priority paths. The resultant
semantics took five pages to write down, but we persevered. After a year’s
experience with the features, we found that each of the members of the Self group
had wasted no small amount of time chasing compiler bugs that were merely
unforeseen consequences of these features. It became clear that the language had
6.10 Self
169
strayed from its original path.
We now believe that when features, rules, or elaborations are motivated by particular
examples, it is a good bet that their addition will be a mistake. The second author
(David Ungar) once coined the term “architect’s trap” for something similar in the
field of computer architecture; this phenomenon might be called “the language
designer’s trap”.
If examples cannot be trusted, what do we think should motivate the language
designer? Consistency and malleability. When there is only one way of doing things,
it is easier to modify and reuse code”.
We fully agree with this statement. The prioritized dynamic inheritance model of PCoC also
causes some problems. It requires that developers learn this different model. On the other hand,
the PCoC approach is simpler. See later.
The prioritized multiple inheritance concept of Self (before version 3.0) combines unordered and
ordered multiple inheritance. That is, on one hand, the parents of an object are ranked by their
priorities. Slots of higher priority parents take precedence over that of parents with a lower
priority. On the other hand, parents at the same priority level are unordered with respect to each
other, and accesses to any clashing slot definitions will generate an ambiguous message error.
parent A
parent B
0
parent C
1
1
parent D
2
3
parent E
<<ordered list
of parents>>
child F
Figure 6.10-2 Prioritized multiple inheritance in Self
In this case, the parent object A has the highest priority (0), the highest ranking. B and C have the
same priority (1). A so-called sender-path tiebreaker rule resolves ambiguities when the same
slots are inherited from different parents with the same priority. Parents can direct subsequent
messages (resends) back to the original receiver, or via other inheritance paths of the original
receiver (in our case child), or to one of their own parents. This makes the concept even more
complex. Read a more detailed description of this concept in [CHAMB91].
Some older programming languages such as New Flavors, CommonLoops, and CLOS, support
ordered multiple inheritance. These languages linearize the inheritance graph, in addition to the
ordering of parents. That is, they construct a total ordering of all classes that is consistent with
each class’ local ordering, defined as the class followed by its direct parents in order.
[CHAMB91] mentions two drawbacks of this linearization: ambiguities between otherwise
unordered parents are ignored, and the concept fails if the local ordering of a class' parents is
inconsistent with the global class ordering.
PCoC neither provides a global ordering of objects or classes (a linearization) like the mentioned
programming languages, nor a mix of ordered and unordered multiple inheritance like the
prioritized multiple inheritance feature of former Self versions (before version 3.0). It only
supports a ranking of acquired parents.
170
6 Comparison with Related Approaches
The mechanism can be used to invoke Activities synchronously or asynchronously. Activities can
be reused for various purposes. For example, they can be associated with menu entries and
toolbar buttons, accessed via scripting and remote interfaces, etc.
PCoC users have a few things to learn:
1. an object can acquire (dynamically inherit) a set of operations (so-called Activities) from
another object—the so-called parent. Acquired parents are ranked (prioritized). The
ranking can be changed at any time.
2. how to send a message in order to perform such an operation. A message is always sent via
the highest priority path which provides the specified operation. Multicasts are also
possible.
3. resends to the original receiver are supported. When an Activity is invoked, it can invoke
other Activities using Dispatchers of the original receiver of the current Activity request.
The receiver (the Activity Set where a Dispatcher has been invoked) is passed as explicit
parameter to the requested Activity. However, requests to other Activity Sets are also
supported.
4. how to register and expose Activities (addActivity) and Activity Sets (specified name
in the constructor of an Activities Provider). See Chapter 4.
The prioritized-acquisition mechanism of PCoC is mainly used for focus management of PCoC
Tools and Service Providers, but is also useful for simple task scheduling, or simply for hiding
the fact that some components may define the same operations. Many components provide
semantically and syntactically equal operations such as getSelection, copy, checkout,
and others. A getSelection message is automatically sent to the component with the highest
priority. This is convenient for developers, since they need not bother about focus management,
and the code stays the same, no matter if operations are invoked via script, GUI, or even if there
is no GUI at all.
Unlike in Self, there cannot be multiple parents with the same priority. This makes the
mechanism easier to understand.
PCoC has some introspection built in to help in cases of confusion. Of course, prioritized
multiple inheritance and dynamic method invocation can be a real burden. However, with an
introspection facility that gives a lot of feedback, developers can get and retain an overview of
which instances of Activity Sets, Dispatchers, Activities, etc. exist at runtime and how they are
related to each other. This helps to understand the behavior of the system and supports us to find
errors. We found that developers are able to use this programming model efficiently and without
getting lost in searching for bugs. For operations that should not be exposed through a user
interface, scripting or remote interfaces, using normal methods is recommended.
See Sections 5.4.3 and 5.6 for details about the acquisition mechanism of PCoC.
6.10.3.2 Design Principles
One way to achieve malleability and reusability is sharing. So, one important concept of Self is
that parents of an object are treated as shared subparts of the object. Message lookup in parents is
done by treating the parents as part of the receiver.
6.10 Self
P
A
P
B
A
171
shared
P
B
Figure 6.10-3 Parents as Shared Objects
The parent P of object A and B is treated as part of A and B. self always refers to the receiver of
a message, regardless of inheritance. Inherited methods are considered to be shared parts of the
same receiver object. For example, a method of P being invoked on A refers to the same receiver
object A. A and P together form one receiver object, as well as B and P. If A cannot handle the
message, it is searched for in parent P, and so on.
One problem of multiple inheritance is to resolve ambiguities between multiply-inherited
conflicting behavior and states (methods and fields). Two or more parents may define the same
slots. Schemes for automatically resolving such conflicts may be confusing to users.
Some older programming languages such as New Flavors, CommonLoops, and CLOS support
ordered (ranked) multiple inheritance. This is useful if the object is more like one parent. This
one parent would have a higher rank than the others.
The opposite approach is to leave the resolving of ambiguities to the programmer. Programming
languages such as Common Objects, Trellis/Owl, Eiffel, C++ treat parents as equals without
ordering. Ambiguities must be resolved explicitly be the developer. This unordered inheritance
works best with relatively unrelated parents.
Both approaches have advantages and disadvantages. Earlier versions of Self combined these
approaches by supporting prioritized inheritance. Parents with the same priority were unordered,
and such with a higher priority had precedence over parents with lower priority. This means that
slots of the parents with a higher priority had precedence over that of lower-priority parents. Slots
of the object itself took precedence over inherited ones. If the same slot were inherited from two
or more parents of the same priority, then the system generated a messageAmbiguous error.
In Self, parent slots are assignable like other slots, so it is possible to change the inheritance
structure at runtime. This feature is called dynamic inheritance.
PCoC uses a similar approach. Parents can be assigned to components at runtime, and also
removed. The corresponding operations are called “acquire” and “discard”. Actually PCoC
supports more a kind of ordered inheritance rather than different priorities and unordered parents
per priority level. The ranking can be changed at runtime. No clashes of slots, i.e., no ambiguous
slots, can occur.
The PCoC inheritance mechanism can produce quite the same annoying effect as the prioritized
multiple inheritance feature of earlier Self versions. The following drawbacks were reasons to
abandon this feature in new versions of Self (borrowed from [CHAMB91], Pages 28-29):
“One consequence of using higher-priority parents to implement mixins in Self is
that these mixin objects should almost always be defined with no parents. Otherwise,
the slots of the mixin’s ancestors, no matter how general, would override any slots in
lower-priority parents, no matter how specific. This is almost never what the
programmer intends.
172
6 Comparison with Related Approaches
Another potentially surprising effect of ordering an object’s parents is that a chain of
resends may eventually “backtrack” and call a method defined in a lower-priority
parent of a descendant of the sending method holder, if no more ancestors of the
descendant’s higher priority parent contain matching slots. This is a desirable feature
of ordered multiple inheritance and resends, since it allows mixins to invoke the
methods that they override, which are defined in lower-priority “cousins.” However,
backtracking to lower-priority branches may surprise the novice programmer in other
situations, especially if the lower-priority branch is a child of the resending method.
Ordered multiple inheritance, resends, and dynamic inheritance have complex
interactions. Dynamic inheritance does not normally affect message lookup, since
assignable parents cannot change while a message send is being handled. However,
they may change between resends within a chain of resends. This would not be a
problem in a system with single inheritance or unordered multiple inheritance, since
the message lookup could always begin with the resending method holder’s parents
and proceed upwards. But with the introduction of ordered multiple inheritance, a
resend might have to backtrack to a lower-priority parent of a descendant of the
resending method holder to find the next matching slot. If a parent between the
receiver and the resending method holder were changed between resends, it would be
difficult to determine what the next matching slot should be, especially if the
resending method holder were no longer an ancestor of the receiver at all.”
Inheriting objects through different paths, and thus also through different priorities, can indeed be
troublesome. But, PCoC does not offer a complex mechanism to continue the search for a
method or better an Activity, when a matching Activity has already been found. Either an
Activity, or a set of Activities in the case of multicasts, is found or not, but the search cannot be
continued. This may be a limitation, but it is still possible to invoke Activities explicitly in a
parent.
Resends to the original receiver (Activity Set) of an Activity request are possible, but the resends
are treated the same way as any other Activity request. More precisely, a resend is the use of a
Dispatcher in the receiver Activity Set which is passed as explicit parameter to the current
Activity. Ambiguities between parents, or better their Dispatchers, does not have to be resolved.
Because of the ranking of parents, there cannot be ambiguities. For a few cases, some Activities
may be acquired through different acquisition branches (an indirect parent can be reached
through different priorities), but we did not encounter problems because of this. Operations that
are inherited from the same component through different paths are only taken from the higher
priority occurrence.
In short, the complexity is quite low. Resends are treated like any other Activity requests, no
ambiguities between parents have to be resolved, and there is no support for continuing the
search for an Activity. There is only one dispatch mechanism, and a request can be sent by
explicitly using a Dispatcher of an Activity Set (the receiver). Read more about the dispatch
algorithm of PCoC in Section 5.6.2.
[CHAMB91] explains the prioritized multiple inheritance feature of earlier Self versions and its
drawbacks in detail.
6.10.4 Remarks
Self is an object-oriented programming language based on prototypes. Objects are created by
cloning objects instead of class instantiation. No class objects are needed. The state information
of objects is accessed solely through messages to “self”, which gave the programming language
its name. Methods are objects associated with code, as opposed to variables. For data there is the
concept of assignment slots. Normally slots are readable, no matter if accessing data or methods.
6.10 Self
173
The developers of Self abandoned prioritized multiple inheritance. They say it was a mistake to
introduce it in an earlier version. Too many ways to do things made it hard to get used of this
programming language. They wanted to keep it minimal, so they removed this feature in version
3.0. Note that Self still supports multiple inheritance, but without prioritization of the parents of
objects.
Self’s problem with managing prioritized inherited operations with the same name from different
parent objects is actually a feature in PCoC. The approach is not as powerful as that of Self, but
therefore easier to understand. The idea of PCoC is to manage cases where an object acquires
from others which may define the same operations, by ranking the acquired parents. Although
PCoC introduces another programming model for invoking methods, it is kept minimal.
Experience has shown that developers understand the basic concepts of PCoC quickly. Problems
during debugging are reduced by supplying some introspection facilities for PCoC. To get
detailed information about PCoC’s priority management, see Sections 5.4.3 and 5.6.
PCoC has also quite the same drawbacks as the prioritized multiple inheritance feature of earlier
Self versions. Inheriting a component through two different branches with different priorities can
be troublesome, although we have not encountered such troubles with PCoC yet (due to its
dispatch algorithm; cf. Section 5.6.2). Changing parent ordering during the process of delegating
messages (and invoking operations) can have strange effects. We try to reduce these effects by
simply letting the framework determine the current receiver paths for a set of Dispatchers before
actually invoking them. If the actual receivers are determined before Dispatchers are invoked,
then priority changes during the execution do not have any impact on the selection of the
Activities to be invoked. This behavior is described for macro-Tasks in Section 4.5.3. In Self, a
priority change during the execution of a method with subsequent resends may lead to surprising
results. Depending on the priority change, the method lookup for the resends may continue with
different methods.
For more information about Self, see [UNGAR87], [CHAMB89], [CHAMB91], [AGESE95],
and [SMITH95]. [LIEBER86] discusses delegation in prototypical systems and may be
interesting in this context.
6.11 System Object Model (SOM, by IBM)
For the sake of completeness, IBM’s System Object Model should also be mentioned here. SOM
is a technology that was designed to overcome several major issues of using object class
libraries. So-called system objects can be distributed and subclassed in binary form. Subclassing
is supported across different programming languages; users may use class libraries in their
preferred programming language.
SOM provides support for the fragile base class problem (when a class and its subclasses can
evolve independently) and subsequent updating. Changing interfaces in base classes or
components affects existing client code. SOM supports upward binary compatibility, which
ensures that client code will still run after updating components.
Another interesting feature of SOM is its naming service. Instances of naming services can be
organized in hierarchies—a feature that is supported by almost any new programming language
(through name spaces, packages) and also by PCoC (through Activity Sets).
Resolution of multiple inheritance is done as follows: when the same method is inherited from
multiple ancestors, the leftmost ancestor specified in the base-class list is used. This mechanism
is called left path precedence rule.
SOM fully supports meta-programming. A program based on SOM can define new interfaces,
synthesize new classes matching them, and create new instances of the new classes. SOM even
allows intercepting the execution of operations. Distributed SOM makes use of this and
integrates distribution support on top of SOM.
174
6 Comparison with Related Approaches
Read more about SOM in [IBMSOM94]. You can register at http://www3.ibm.com/software/ad/som/som30tk.html for more SOM documentation and reference guides.
6.12 Design Patterns
While developing PCoC the question arose whether or not Activities and Dispatchers are similar
to known patterns of software engineering literature. This section answers this question by
comparing PCoC patterns to existing ones. Detailed descriptions of common design patterns
mentioned here can be found in [GAMMA95].
6.12.1 Command Pattern
A command is an operation encapsulated as an object. Although an Activity also encapsulates an
operation as an object, there are not many other similarities between these concepts. Command
objects are more basic. They usually allow do, undo, and redo-operations. In order to execute
a command, a command object must be created. For example, on the selection of a menu entry
(e.g., “undo”), such a command object may be created. In contrast, Activities are usually created
only once per component. They must already be present in order to perform them. An Activity
may create a command object on its execution. However, itself is no command object.
6.12.2 Strategy Pattern
The strategy pattern provides a concept to define sets of algorithms, to encapsulate them in
classes, and to vary algorithms independently of clients that use it. For example, we may define
various classes with the same interface, each implementing a different graph layout algorithm.
Such a class is called strategy. In PCoC, the strategy pattern is used for Dispatchers to gather
acquired Activities (Activities from acquired Activity Sets) using the specified directives, and
subsequently to invoke different methods of the resulting Activities, for example getState,
perform, getContextData. The search algorithm is implemented just once. See Sections
4.4 and 5.6.2. This is useful, since such algorithms are often complex, and it is reasonable to
maintain it only once in the source code. The strategy objects do the actual work on the objects
delivered by the algorithms.
Although, Dispatchers make use of the strategy pattern, neither Dispatchers, nor Activities are
strategy objects.
6.12.3 Activities: First Class Objects
Some of the patterns used in PCoC are conventional ones, such as bridge, singleton, observer,
strategy pattern or combinations of these. Some patterns may be, in part, similar to existing
patterns.
Basically, Activities are method objects. On framework-level, they are treated as first-class
objects. They can be created, associated with a concrete method, added, and removed at any time.
Activities are also similar to the command pattern, or Java Swing Actions (see Section How to
Use Actions in [WALRA99]), but actually they are quite different (see the previous sections).
Activities are similar to method descriptors of Java Beans, but additionally make it possible to
add and remove them at runtime, to specify and change state, for example, “Enabled” or
“Disabled”, and provide additional context data, for example, data additional to return value of
an Activity delivering the current selection in a text editor Tool.
See [ENGLA97] on Pages 205ff, for an explanation of method descriptors. See also the Sections
Using Java Reflection and Finding the method, and Pages 41f for the basics of the Java
reflection.
6.12 Design Patterns
175
6.12.4 Dispatchers
There is no pattern for gathering possible receivers of requests ranked by priority (depending on
the focus history of their components) and subsequently sending the requests. Dispatchers might
be used without Activities, for example, with method references as in Java reflection, or as
function pointers, etc. These actually do not require Activities.
However, Dispatchers can be described as smart method references holding direct or indirect
references to Activities (which, in fact, are method objects); they provide forwarding and
delegation capabilities and can be deployed independent of other Dispatchers; Dispatchers are
bound to a specific Activity name and interface (similar to method signature).
176
6 Comparison with Related Approaches
7 Conclusion
177
7 Conclusion
This chapter summarizes features, advantages, and disadvantages of the framework presented in
this thesis. It describes the impact of PCoC on real applications, and impressions and feedback
provided by current users of PCoC.
7.1 Overview
Through its support for dynamic component modification and delegation, PCoC can be used to
introduce some flexibility into applications. Component functionality, as well as its look and
feel, can be customized at runtime, separating the implementation process from the
customization. Operations, or more precisely Activities, can be reused for various application
interfaces such as the user interface, scripting, remote control, etc., thus they need only be
implemented once. The framework automatically exposes Activities, or better their Dispatchers,
for their use with these interfaces.
The use of thin base classes provided by PCoC as templates for components can help to further
reduce the implementation effort. The method dispatch mechanism including its support for
different component interfaces is hidden in the base classes, as well as the processing of
component startup, initialization, and termination. This approach allows developers to
concentrate on value-added tasks such as implementing the core logic (the purpose) of each
component.
7.2 Which Mechanisms When?
This section is the result of many team discussions of framework architects at Takefive Software,
respectively WindRiver Systems, including the author of this thesis.
7.2.1 Static Interfaces / Methods
Static interfaces, or more precisely ordinary methods or functions, should be used in cases where
tight binding is necessary or desired, for example, for high performance, more convenience,
syntax checking at compile time, simple static source code browsing, etc.
Advantages:
v Compile-time type checking for method arguments and return values helps to find errors
earlier than with dynamic method calls or other reflection-based dispatch mechanisms,
where types can only be checked at runtime.
v Static method invocation keeps client code small and readable, as opposed to reflectionbased mechanisms where arguments and return values mostly must be packed into special
container objects, and type-casts are necessary (e.g., in Java, from Object to concrete
types), and where the method call itself is more complicated.
v As opposed to reflection-based mechanisms, static method invocation is a widely
understood and preferred model for synchronous calls.
Disadvantages:
v Methods should only be used for non-blocking and short-term operations. For long-term
operations which block the current thread, asynchronous concepts such as events should be
used.
v Methods are not suitable for asynchronous calls. For deferred execution, we need an object
that represents an operation including its arguments. For such purposes, command objects
or events should be used. PCoC supports Tasks for this purpose.
178
7 Conclusion
v Methods are not suitable for connecting user interface (menu, toolbar) code to components.
For example, there is no support for state-change notifications (“Enabled”, “Disabled”,
etc.), customization, visual representation, etc. For this purpose, for example, command
objects, Java Swing actions, or PCoC Tasks, are better suited.
7.2.2 Events
Events are the most general form of component communication and can also be used to model
dynamic dispatch.
Advantages:
v The model and usage is commonly accepted and understood.
v Events can be used for one-to-one, one-to-many, and two-way communication.
v An event system can be structured to prevent deadlocks and race conditions. Events in a
queue can be rearranged in order to prevent deadlocks depending on the resources they
require, whereas synchronous method calls cannot be rearranged in a call stack.
Disadvantages:
v Events are less comfortable to use than methods. For example, arguments must be packed
into the event object, and thus cannot be passed as easy as with methods. Debugging is
more difficult, because we lose information about when an event has been sent (there is no
useful backtrace).
v Events are objects which must be assembled and put into a queue. This causes a
performance penalty compared to static interfaces.
7.2.3 PCoC / Dynamic Dispatch
Activities and Dispatchers provide something that is missing or at least less satisfying in Java
and other programming languages: flexible method dispatch, including delegation and the
possibility to add operations to components or objects at runtime, or to modify them.
Java provides a similar concept with Swing Actions, but they cannot take arguments, do not have
return values, and there is also no delegation mechanism.
.NET delegates are similar to Dispatchers, but they are less dynamic. They cannot be declared
and bound to methods at runtime, but they are nevertheless very useful for various purposes such
as multicasts, as strategy objects in algorithms, etc.
.NET provides with the name space System.Reflection.Emit a facility that allows to
define and create assemblies, modules, types and methods at runtime. With PCoC, Activities and
Activity Sets can be defined and created at runtime. For the invocation of Activities, Dispatchers
are used.
As opposed to the Dispatcher dictionaries of PCoC, v-tables are static and cannot be modified at
runtime.
As opposed to PCoC Activities and Dispatchers, the Smalltalk method dispatch does not offer
support for method states, attaching of listeners to method references, and multicasts.
Advantages of the flexible method dispatch mechanism, respectively PCoC, are:
v PCoC supports single- and multicasts through Dispatchers. Specified directives (e.g.,
first or broadcast) determine how requests are delegated.
v Activities can be defined, added, and removed at runtime. This introduces some flexibility
into a system.
7.2 Which Mechanisms When?
179
v PCoC Tasks can be used for synchronous and asynchronous calls. For asynchronous calls,
Tasks are put into a queue (task.performLater()).
v Tasks, Dispatchers, and Activities provide support for states and state notifications
(“Enabled”, “Disabled”, etc.) This feature is needed for menu entries, toolbar buttons,
scripting (for checking the availability and state of an operation), etc.
v Activity Sets can be organized in hierarchies in order to group semantically related sets, and
therefore providing dynamic scopes.
v Activity Sets can acquire others, respectively their Dispatchers. This concept is useful to
share behavior between components.
v License management is handled generically. It is only necessary to specify a license string
when creating an Activity or Activities Provider. The license management is done by the
framework (using a license management library such as FlexLM).
v PCoC supports reusability of code. Activities are generically exposed through various
application interfaces, including user interfaces (for menu and toolbar entries), scripting,
remote control interfaces (e.g. XMLRPC), and for the use within the source code of the
components providing them.
Disadvantages:
v The PCoC approach leads to an increased memory footprint compared to method pointers.
v Type-checks are only possible at runtime, since Activities are invoked dynamically.
v It is difficult to get an overview of the relationships between Activity Sets, Activities,
Dispatchers, and Tasks through source-code browsing. Although there are sophisticated
introspection facilities available to inspect the relationships at runtime, and detailed error
logs for type- and dispatch-errors, etc., the search for errors is not as efficient as with more
static approaches like classes and methods. Some annoying drawbacks of a prioritized
multiple inheritance mechanism such as that of PCoC and in the programming language
Self are discussed in Section 6.10.3.
v The PCoC approach is initially unfamiliar to users. Using Activities instead of only
methods and sending requests through Dispatchers is less comfortable than ordinary method
calls. For example, arguments must be packed into argument containers (instances of
Material), and there are different directives for single- and multicasts, etc.
v PCoC leads to a performance penalty compared to static interfaces (methods, functions),
since Activities, Dispatchers, Activity Sets, etc., must be created and managed. However,
this approach has not been designed for performance relevant purposes, but rather for
medium- to long-term operations.
7.3 Remarks
Each of the mentioned dispatch mechanisms solves a specific, somewhat orthogonal problem
quite well. Even though Activities and Dispatchers can be considered as a specialization of
methods, initial user acceptance is hesitant. Nevertheless, experience has shown that developers
begin to like the concept and understand its strength after having tried it for a few components.
PCoC is not a replacement for interface standards such as DDE, COM, CORBA, .NET,
XMLRPC, etc. It just provides features missing in most programming languages, interface
standards, and frameworks: dynamic scopes, delegation, dynamic component modification, and
priority management for components and operations.
Some developers require PCoC to support static binding, for example, in order to invoke
Activities like normal methods rather than using dynamic invocation with Dispatchers; they
180
7 Conclusion
require Activities that are, like methods, statically bound to classes, and maybe not even separate
objects; argument and result types should be checked at compile-time, etc. This is not what
PCoC has been designed for. In such cases, static interfaces (functions, methods) should be used.
However, if an application must support control via scripting, or there is a need for a general
concept for events and dynamic method invocation, PCoC can save development effort. It can be
used for operations that are generally user or script driven. Although the performance is quite
good, it is not good enough to use Dispatchers, for instance, in performance-critical program
loops.
Disadvantages and limitations of PCoC are the increased memory consumption and lower
performance compared to language supported facilities, since each Activity, Dispatcher, Activity
Set, etc., is a separate object.
When using PCoC, we have to worry about the number of Activities to be created, in order to
keep the memory consumption low, and for security reasons. It would not be acceptable to
expose each method as an Activity. Whenever ordinary methods can be used, they should be
used.
Note that some memory and performance is consumed through reflection mechanisms already
supported by platforms such as, for example, .NET and Java. Besides that, current applications
generally consume a lot of memory, so that the portion of PCoC is rather irrelevant. For example,
a concrete application has an average consumption of about 2.1 MB for the framework (100
loaded components and Activity Sets × approx. 200 bytes, 5000 Activities × approx. 160 bytes,
10000 Dispatchers × approx. 40 bytes, 2000 Tasks × approx. 240 bytes), whereas the whole
application needs over 200 MB. We cannot give more precise numbers, since the memory
consumption depends on many factors. Beside the number of objects, it depends on the number
of acquisition relationships between Activity Sets, the complexity of Tasks, the number of
different Activity interfaces (equal interfaces are shared), the string lengths of names, paths, etc.
In any case, development and maintenance costs are much more crucial for software
development than memory consumption (the latter is also very important to be considered,
though).
7.4 Discussion
The following questions arose during the development of PCoC.
Why do we choose such a design?
It has proved to support rapid application development and needs low effort to train developers.
It does not claim to be suitable for every kind of component, but for a large area its use makes
sense. The modular architecture and the dynamic binding between components enables
modifying existing components without the need to recompile components that use them.
Components can be loaded on demand, or unloaded, or replaced by other implementations
dynamically.
Is it not better to provide tool developers with a maximum of freedom
instead of providing component templates?
First of all, for intra-component communication it makes no or not much sense to use PCoC.
Within a component, developers can use programming languages, language constructs, patterns,
etc., as they like and as it makes sense.
However, for operations that must be accessible via several application interfaces, or used for
inter-component communication, it makes sense to use PCoC concepts. This includes interfaces
such as menus, toolbars, scripting, remote invocation, and other interfaces. PCoC guides us
7.4 Discussion
181
through component development by providing component templates. We can concentrate on
value-added tasks such as implementing the core functionality (the concrete purpose) of a
component, rather than having to care about component interoperation, different component
interfaces, existence of other components, startup and termination issues, etc.
The Eclipse platform of OTI, an IBM research institution, is another good example of how a
framework can provide some simple rules to create larger applications. Their plugin architecture
is in some ways similar to that of PCoC. It has built-in support for component startup,
termination, configuration, interoperation with other components, etc. See [OTI1001] and
[OTI1101].
But what is it good for? Where is the gain?
PCoC offers a uniform message dispatch mechanism that can be used for various purposes. This
includes synchronous and asynchronous (deferred) invocation of operations, delegation, licensemanagement for operations (Activities can be associated with license strings), focus
management, state support for operations, including change notifications (“Enable”, “Disable”,
etc.), wrapping operations in order to add aspects, and dynamic component modification. The
support for various component standards (COM, CORBA, etc.) is encapsulated in the PCoC
dispatch mechanism. This makes client code smaller, more readable, and easier to maintain.
Because of the uniform way to solve different problems, this approach also reduces training time
and costs for new developers.
How long does it take to educate a new developer to make him
understand an architecture and able to implement a new component?
With PCoC, we saw that developers can learn the necessary things to start implementing their
first component within a few hours. They can immediately concentrate on implementing the
actual functionality of their components, instead of thinking about the architecture and
component interfaces of an application, and of re-inventing solutions for various issues.
7.5 Future Work
We are currently introducing a threading model to PCoC; this should help component developers
to reduce the effort of managing common threading issues such as deadlocks by automatically
rearranging Tasks in a queue, depending on the required resources.
Beside possible deadlocks, there are other issues such as blocking the AWT event dispatch
thread in Java programs, and the missing read/write capabilities of semaphores. Java semaphores
do not support simultaneous read-only locks, while blocking write locks, and vice versa. For
more information about current threading issues in Java, see [HOLUB02] on Pages 275ff.
Work is in progress to forward SOAP calls to invocations of PCoC Tasks, which would
standardize the interprocess communication with other applications. Since the structures of these
two facilities are similar, they can be easily mapped.
Another idea is to use the framework for task scheduling, but we have not started on this, yet.
PCoC could also be used for managing administrative processes in firms, and especially in
factories. For example, in a firm a manager requires tasks to be executed. He or she delegates
tasks to workers on different tools and devices, or delegate them to services. Each task can again
consist of subtasks. Materials are needed in order to execute tasks, and tools and workers must be
available. If a tool or worker is no further available, or the used material is not appropriate, the
manager will be notified. PCoC provides facilities for this kind of administration. The concepts
and processes are quite similar. There are managers called Dispatchers, responsible for
delegating the execution of specific activities. A task is a combination of activities performed on
different devices or tools and/or by different services.
182
7 Conclusion
183
Bibliography
[ACQU00] Simpson E., Untangling Acquisition with Trees,
http://www.zope.org/Members/4am/aq_trees, 2000
[ACQU01] Udell J., Tangled in the Threads: Nature vs. Nurture, BYTE Magazine,
http://www.byte.com/documents/s=705/BYT20010614S0001/, 6, 2001
[ACQU98] Fulton J., Zope Corporation, Acquisition,
http://www.zope.org/Members/michel/Projects/Interfaces/ReleaseDocumentation,
http://www.ccs.neu.edu/home/lorenz/research/acquisition/,
http://www.zope.org/Members/jim/Info/IPCB/AcquisitionAlgebra/siframes.htm, 1998
[AGESE95] Agesen O., Hölzle U., Type Feedback vs. Concrete Type Analysis: A Comparison of
Optimization Techniques for Object-Oriented Languages, OOPSLA '95 Conference
Proceedings, http://www.cs.ucsb.edu/oocsb/papers/oopsla95-tf.pdf under
http://www.cs.ucsb.edu/oocsb/papers/tf-vs-ti.html, ACM SIG-PLAN, 1995
[BLOSS00] Blosser J., Explore the Dynamic Proxy API, Java World,
http://www.javaworld.com/javaworld/jw-11-2000/jw-1110-proxy_p.html, 11, 2000
[BREYM02] Breymann U., Loviscach J., Die neue C-Klasse: C# im Vergleich mit C++ und
Java, c't: Magazin für Computertechnik, http://www.heise.de/ct, Heise, 2, 2002
[CHAMB89] Chambers C., Ungar D., Lee E., An Efficient Implementation of SELF, a
Dynamically-Typed Object-Oriented Language Based on Prototypes, OOPSLA '89
Conference Proceedings, http://research.sun.com/self/papers/oopsla89.ps.Z under
http://research.sun.com/self/papers/implementation.html, ACM SIG-PLAN, 1989
[CHAMB91] Chambers C., Ungar D., Hölzle U., Chang B.-W., Parents are Shared Parts of
Objects: Inheritance and Encapsulation in SELF, LISP and Symbolic Computation,
http://research.sun.com/self/papers/papers.html, Computer Systems Laboratory, Stanford
University, 1991
[COPLI91] Coplien J. O., Advanced C++: Programming Styles and Idioms, ISBN0-201-548550, Addison-Wesley, 1991
[CZARN01] Czarnecki K.,Dominick L., Eisenecker U. W., Aspektorientierte Programmierung
in C++: Teil 1, Erste Aussichten, 2001
[DAVID00] Davidson M., Using the Swing Action Architecture,
http://java.sun.com/products/jfc/tsc/articles/actions/index.html, 2000
[ENGLA97] Englander R., Developing JAVA Beans, ISBN1-565-92289-1, O'Reilly, 1997
[FOOTE89] Foote B., Johnson R. E., Reflective Facilities in Smalltalk-80, OOPSLA '89
Conference Proceedings, ftp://www.laputan.org/pub/foote/Reflection.rtf, ACM SIG-PLAN,
1989
[GAMMA95] Gamma E., Helm R., Johnson R., Vlissides J., Design Patterns: Elements of
Reusable Object-Oriented Software, ISBN0-201-63361-2, Addison-Wesley, 1995
[GIL96] Gil J., Lorenz D., Environmental Acquisition: A New Inheritance-Like Abstraction
Mechanism, OOPSLA 1996 Conference Proceedings, http://www.belllabs.com/people/cope/oopsla/Oopsla96TechnicalProgramAbstracts.html#GilLorenz,
http://www.cs.technion.ac.il/Labs/Lpcr/publications/lpcr9507.html,
http://www.ccs.neu.edu/home/lorenz/research/acquisition/, 1996
[GITTI00] Gittinger C., Die Unified Smalltalk/Java Virtual Machine in Smalltalk/X, OOP 2000
Conference Proceedings, 101communications LLC, 2000
184
[GOLDB83] Goldberg A., Robson D., Smalltalk-80: The Language and its Implementation,
ISBN0-201-11371-6, Addison-Wesley, 1983
[GRIFF98] Griffel F., Componentware: Konzepte und Techniken eines Softwareparadigmas,
ISBN3-932-58802-9, dpunkt, 1998
[HARRIS97] Harrison W., Ossher H., Tarr P., Using Delegation for Object and Subject
Composition, Research Report RC 20946 (922722),
http://www.research.ibm.com/sop/abstracts/delegation.htm, 1997
[HOLUB02] Holub A., Taming Java Threads, ISBN1-893-11510-0, Apress, 2000
[IBMSOM94] Object Technology Products Group, The System Object Model (SOM) and the
Component Object Model (COM), http://www4.ibm.com/software/ad/som/library/somvscom.html, 1994
[IBMST95] Khor K. K., Chavis N. L., Lovett S. M., White D. C., IBM Smalltalk Tutorial,
http://media.dml.cs.ucf.edu/COP4331/Tutorials/Smalltalk/, 1995
[ISOCPP98] American National Standards Institute, Programming Languages--C++, ISO/IEC
14882, 1998
[iX1201] Stal M., C#- und .NET-Tutorial: Teil 1, iX: Magazin für professionelle
Informationstechnik, http://www.ix.de/ix/artikel/2001/12/122/, Heise, 12, 2001
[JDK12] Sun Microsystems, Java 2 SDK, Standard Edition Documentation, Version 1.2.2,
http://java.sun.com/products/jdk/1.2/docs/api/index.html, 1999
[JDK13D] Sun Microsystems, Java 2 SDK, Standard Edition Documentation: Dynamic Proxy
Classes, http://java.sun.com/j2se/1.3/docs/guide/reflection/proxy.html, 2001
[JDK13P] Sun Microsystems, Java 2 SDK, Standard Edition Documentation: Class Proxy,
http://java.sun.com/j2se/1.4/docs/api/java/lang/reflect/Proxy.html, 2001
[KICZAL00] Kiczales G., et al, An Overview of AspectJ, technical report,
http://aspectj.org/documentation/overview/aspectj-overview.pdf, 2000
[KNIES98] Kniesel G., Delegation for Java: API or Language Extension?, Technical Report
IAI-TR-98-4, ISSN 0944-8535, http://javalab.cs.uni-bonn.de/research/darwin/papers.html;
http://javalab.cs.uni-bonn.de/data2/papers/darwin/patterns.IAI-TR-98-5.pdf, 1998
[KNIES99] Kniesel G., Type-Safe Delegation for Run-Time Component Adaptation, Proceedings
of ECOOP99, http://javalab.cs.uni-bonn.de/research/darwin/papers.html, 1999
[KRASN84] Krasner G., Smalltalk 80: Bits of History, Words of Advice, ISBN0-201-11669-3,
Addison-Wesley, 1984
[LIBERT01] Liberty J., Programming C#, ISBN0-596-00117-7, O'Reilly, 2001
[LIEBER86] Lieberman H., Using Prototypical Objects to Implement Shared Behaviour in
Object Oriented Systems, OOPSLA '86 Conference Proceedings,
http://lcs.www.media.mit.edu/people/lieber/Lieberary/OOP/Delegation/Delegation.html,
http://lcs.www.media.mit.edu/people/lieber/Lieberary/OOP/OOP.html, ACM SIG-PLAN,
1986
[LIU96] Liu Ch., Smalltalk, Objects, and Design, ISBN0-132-68335-0, Manning Publications,
1996
[LUTZ01] Lutz D., Aspekt-Orientierte Programmierung, OOP 2001 Conference Proceedings,
101communications LLC, 2001
[MAETZ97] Mätzel K. U., Schnorf P., Dynamic Component Adaption, Technical Report 97-6-1,
http://www.goeast.ch/maetzel/Publicationlist.html, 1997
185
[MICROS01] Microsoft, Reflection: Leveraging the Power of Metadata, Microsoft .NET
Developer Tools Readiness Kit, NETRK CD:/modules/Reflection.ppt, 2001
[MONO03] Ximian, Inc., Mono, http://go-mono.com/, 2003
[MÖSS02] Beer W., Birngruber D., Mössenböck H., Wöß A., Die .NET- Technologie:
Grundlagen und Anwendungsprogrammierung, ISBN3-898-64174-0, dpunkt, 2002
[MÖSS91] Mössenböck H., Wirth N., The Programming Language Oberon-2, technical report,
http://www.oberon.ethz.ch/books.html, 1991
[MÖSS95] Mössenböck H., Objektorientierte Programmierung in Oberon-2, ISBN3-540-600620, Springer, 1995
[MÖSS98] Mössenböck H., Objektorientierte Programmierung in Oberon-2, ISBN3-540-577890, Springer, 1998
[MSCSH01] Microsoft, C#, Microsoft .NET Developer Tools Readiness Kit, NETRK
CD:/modules/CSHARP.DOC, 2001
[MSCTS01] Microsoft, Common Type System, Microsoft .NET Developer Tools Readiness Kit,
NETRK CD:/modules/Common_Type_System.DOC, 2001
[MSDLG99] Microsoft Corporation, The Truth about Delegates,
http://msdn.microsoft.com/visualj/technical/articles/delegates/truth.asp, 1999
[MSNET01] Microsoft, .NET Framework Overview, Microsoft .NET Developer Tools Readiness
Kit, NETRK CD:/modules/NET_Framework_Overview.doc, 2001
[OTI1001] Arsenault S., Contributing Actions to the Eclipse Platform, Technical
Documentation, http://www.eclipse.org/articles/index.html;
http://www.eclipsecorner.org/articles/index.html, 2001
[OTI1101] Springgay D., Creating an Eclipse View, Technical Documentation,
http://www.eclipse.org/articles/index.html;
http://www.eclipsecorner.org/articles/index.html, 2001
[ROSSUM90] Rossum G. v., What is Python?, http://www.python.org/doc/essays/blurb.html,
1990
[SMITH95] Smith R. B., Ungar D., Programming as an Experience: The Inspiration for SELF,
ECOOP '95 Conference Proceedings, http://research.sun.com/self/papers/programming-asexperience.ps.Z under http://research.sun.com/self/papers/programming-asexperience.html, ACM SIG-PLAN, 1995
[STAL01] Stal M., Reich der Mitte: Die Komponententechnologien COM+, EJB und "CORBA
Components", OOP 2001 Conference Proceedings, 101communications LLC, 2001
[STROU97] Stroustrup B., The C++ Programming Language, ISBN0-201-88954-4, AddisonWesley, 1997
[SUNDLG99] The Java Language Team, JavaSoft, Sun Microsystems, About Microsoft's
Delegates, http://www.javasoft.com/docs/white/delegates.html, 1999
[SZYPE98] Szyperski C., Component Software: Beyond Object-Oriented Programming, ISBN0201-17888-5, Addison-Wesley, 1998
[TOEDT01] Tödter K., Let's Swing: JFC für Fortgeschrittene, OOP 2001 Conference Handout,
http://www.toedter.com, 2001
[TROEL01] Troelsen A., C# and the .NET Platform, ISBN1-893-11559-3, Springer, 2001
[UNGAR87] Ungar D., and Smith R. B., SELF: The Power of Simplicity, OOPSLA '89
Conference Proceedings, ACM SIG-PLAN, 1987
186
[WALRA99] Walrath K., Campione M., The JFC Swing Tutorial: A Guide to Constructing
GUIs, ISBN0-201-43321-4, Addison-Wesley, 1999
[WESTP01] Westphal R., Einführung in die Microsoft .NET Plattform, OOP 2001 Conference
Proceedings, 101communications LLC, 2001
[WIRTH92] Wirth N., Gutknecht J., Project Oberon: The Design of an Operating System and
Compiler, ISBN0-201-54428-8, Addison-Wesley, 1992
[XEROX02] AspectJ Team, The AspectJ Programming Guide, technical article,
http://aspectj.org/doc/dist/progguide/, 2002
[XEROX0297] Mendhekar A., Kiczales G., Lamping J., RG: A Case-Study for Aspect-Oriented
Programming, Technical report SPL97-009, P9710044,
http://www.parc.xerox.com/csl/groups/sda/publications/papers/PARC-AOP-RG97/, Xerox
Palo Alto Research Center, 1997
[XEROX0697] Kiczales G., Mendhekar A., Maeda C., et al, Aspect-Oriented Programming,
http://www.parc.xerox.com/csl/groups/sda/publications/papers/Kiczales-ECOOP97/,
Xerox Palo Alto Research Center, 1997
[XEROX1297] Kiczales G., Lopes C. V., D: A Language Framework for Distributed
Programming, Technical report SPL97-010, P9710047,
http://www.parc.xerox.com/csl/groups/sda/publications/papers/PARC-AOP-D97/, Xerox
Palo Alto Research Center, 1997
[ZULLIG98] Züllighoven H. et al., Das objektorientierte Konstruktionshandbuch, ISBN3-93258805-3, dpunkt, 1998
187
Index
A
Acquisition
acquisition algorithm 96
acquisition graph
98
changing priorities 100
direct acquisition
36, 95
dispatch algorithm 107f
environmental acquisition 95, 123
examples
37, 83, 96, 98, 100, 124, 126f
explicit
127
implicit
126
Activities Provider
activation
56
adding Activities
64f, 117, 120
containment hierarchy
91
definition
45
examples
77, 82, 84, 93f, 99
in detail
89
relationships 90, 105
removing Activities 65
termination 56, 86
Activity
adding Activities
30, 35, 64f
class interface 59f
COM+ activities
50
definition
49
dynamic type 62
examples
23, 30, 35, 56, 63f, 66, 69,
104, 119
flexible method dispatch
21
generic Activity
117
implementation
66, 103, 117
in detail
101
interface class 102
invocation
67
relationships 90, 105
removing Activities 65
state 67ff
type safety
103
using Activities
59
Activity Set
adding Activities
29f, 35, 65, 117, 120
changing priorities 100
definition
46
direct acquisition
95
dispatching requests 72, 105, 114
examples
23, 27
flexible method dispatch
21
in detail
112
invocation of Activities
28, 34, 105,
114
relationships 90, 105, 113
removing Activities 65
Activity Set Registry
definition
23, 46
examples
23, 27
Aspect-oriented programming
advice 131
AspectJ
128f
aspects
131
in PCoC
133
join points
129
overview
127
pointcut
130
typical aspects
128
using name spaces 132
C
C#
delegates
152
dynamic method invocation 156
name spaces 158
overview
150
properties
161
C++
method pointers
148
v-table
9
Combined Tool
definition
47
examples
48
Configuration
definition
88
examples
57f, 70, 76, 80, 94, 109
Containment Hierarchy
changing the container
39, 92
examples
91
in detail
91
modification 39, 92
overview
39
paths 91
Context Data
definition
60
examples
69f
using Activities
69
using Dispatchers
74
using Tasks 69f
188
D
Darwin
classification 16
examples
17
overview
15
type-safe delegation 15
Delegation
delegation vs. forwarding
13
drawbacks of simulation techniques 17
examples
13, 34, 83
flexible delegation 33
in PCoC
82
in Self 167, 169
overview
12
type-safe delegation 15
using Activities
82
using Dispatchers
83
Developer Roles
component developer 6
customizer / configurator
6
framework developer 6
Dispatcher
context data 74
definition
51
delegation
34, 72
dictionary
24, 115
dispatch algorithm 107f
dispatching requests 106
examples
27, 72
flexible method dispatch
21
implementation
25f, 107f
in detail
105
invocation
26f, 31, 71, 105
proxies
114
relationships 22, 71, 90, 105
state 71, 74
using Dispatchers
71
Dispatcher Dictionary
examples
27
using 27
Dynamic Component Modification
classification 16
declared parents / children 18
examples
17
overview
15
E
Events
advantages 178
channeling
133
disadvantages 178
F
Flexible Dispatch
adding aspects
29
advantages 40
changing priorities 100
direct acquisition
36
disadvantages 42
dispatch algorithm 20, 107
dispatch code 22
Dispatcher dictionary 24
examples
21
linked Dispatchers 21
notifications 31
overview
3, 19
using reflection
20
Forwarding
delegation vs. forwarding
13
J
Java
action listeners
143
actions
143
dispatching actions 145f
dynamic classes
137f, 140
dynamic method invocation 135
overview
134
ranking for multiple proxy interfaces
141
reflection
134
Swing actions 142
v-table
9
L
Lava
type-safe delegation 15
Listener
Activity listener interface
Activity state notification
M
Material
class interface 62
definition
50
in detail
61
Message Objects
advantages 12
disadvantages 12
dispatch code 11
message interpreter
overview
10
Method Dictionary
Smalltalk
8
10
60
68
189
critical issues 150
Priority Management
delegates
151f
flexible method dispatch
37
dynamic method invocation 157
in PCoC
4f
dynamic modifications
162f
in Self 168f
indexers
161
ordered dispatch of Activity requests
late binding 156
72, 101, 105, 108
multicasts with delegates
155
ordered dispatch using Tasks
78, 81,
name spaces 158
111
object model 150
ordered multicasts
38
overview
149
ordered multiple inheritance 171
properties
161
prioritized (ranked) acquisition
4, 37,
97, 101, 171
Multicast
prioritized multiple inheritance
168f
dispatch code 38
unordered multiple inheritance
171
Dispatcher directive 72, 178
flexible method dispatch
20, 38
S
ordered multicast
41, 98, 108
SELF
overview
2
delegation
169
using .NET delegates 151, 155
message
dispatch
171
using Java Swing actions
148
overview
166
using Oberon message objects
12, 164
prioritized multiple inheritance
168, 171
prioritized parents
169
N
ranked
multiple
inheritance
169
Notifications
Activity states
68
Service Provider
Dispatchers 31
definition
48
examples
32, 68
examples
77, 82, 84, 94, 99
listener interface
60
Simple Tool
definition
47
O
examples
48, 55, 93
Oberon
Smalltalk
advantages of messages objects
164
dispatch algorithm 9
disadvantages of message objects 165
method dictionary
7f
dispatch code 11
method
lookup
24
message interpreter 10
overview
7, 163
message objects
10, 164
State
P
handling Activity states
67
invalidation 68
Patterns
retrieving an Activity state 57
Activities
174
update 68
command pattern
174
Dispatchers 175
Static Interfaces
strategy pattern
174
advantages 177
disadvantages 177
PCoC
advantages 178
System Object Model
case-insensivity
52
overview
173
class hierarchy
53
T
disadvantages 179
discussion
180f
Task
future work 181
configuration 58f, 70, 76, 81, 109
overview
4, 55
creation
110
related packages
88
definition
52
what is PCoC?
4
dispatching requests 111
what is PCoC not? 5
examples
58f, 76, 78ff, 109, 112
190
implementation
110
in detail
109
invocation of Activities
111
macro 79
preconditions 79
preserving paths
81
relationships 75, 90
using Tasks 75
WAM-metaphor
52
Tool
definition
47
examples
47, 55, 64, 93
Type-Safe Delegation
Activities
103
classification 16
Darwin
15
examples
17
V
V-Table
dispatch algorithm
index calculation
optimized
19
overview
7, 9
reorganization
10
10, 40, 42
42
Lebenslauf
9. Feb. 1972
Geboren in Braunau, Oberösterreich
1978 - 1982
Volksschule Altheim
1982 - 1986
Hauptschule Altheim
1986 - 1991
HTBLA Braunau / Zweig Informatik
Mai 1991
Matura mit gutem Erfolg
Juli 1991 Feb. 1993
Militärmusik Linz / Ebelsberg
März 1992
Beitritt Shotokan Karate International / Österreich
Okt. 1992 –
Mai 1997
Informatikstudium an der Johannes Kepler Universität Linz
Während des Studiums tätig bei:
Technodat GmbH, Salzburg (Programmierer)
TakeFive Software GmbH, Salzburg (Programmierer)
Seit 1995
Karatetrainer
Mai 1997
Studienabschluss und Sponsion zum Dipl.-Ing.
Juli 1997
Anstellung als Softwarearchitekt bei WindRiver GmbH, Salzburg (vormals
TakeFive Software GmbH)
Seit 1998
Obmann Shotokan Karate International / Landesverband Oberösterreich
Seit 1999
Mitglied des Nationalteams bei Shotokan Karate International / Österreich
Okt. 2000
Inskription zum Doktoratstudium der technischen Wissenschaften
Seit Nov. 2002 Anstellung als Softwareentwickler bei eurofunk Kappacher, St. Johann / Pg.

Similar documents