Stéphane Ducasse

Marcus Denker

Adrian Lienhard

Evolving a Reflective Language Lessons Learned from Implementing Traits

Keywords: D.3.2 [Programming languages]: Object-oriented languages; D.2.3 [Software Engineering]: Coding Tools and Techniques traits, object-oriented languages, smalltalk, reflection, software evolution

Traits are method groups that can be used to compose classes. They do not have a runtime existence and are conceptually folded into the classes that use them. Traits have been implemented in different languages. While implementing them in Smalltalk, our first reflex was to take advantage of the fact that traits are not run-time entities: we optimized the implementation for space and hence shared methods between traits and classes. However, by doing so we broke the introspective API of Smalltalk.

This paper illustrates a more general problem seen in all reflective systems: the implementation serves both as a model for execution and as the model that is exposed to the programmer. There is a conflict of interests between the information necessary for execution and the information the programmer is interested in. In addition, as soon as the implementation is exposed via reflection, we are not free to optimize. As the complete implementation is visible reflectively, there is no way to hide the optimizations.

Few papers report errors and this is one of them. We report our experience facing the initial API mismatch, which has a significant impact on the system because the language is reflective (i.e., written in itself and causally connected). We present the new introspective API we put in place.

REFLECTION ON LANGUAGE FEATURES

Reflective languages as described by the two following definitions have a causally connected representation of themselves.

"Reflection is the ability of a program to manipulate as data something representing the state of the program during its own execution. There are two aspects of such manipulation: introspection and intercession. Introspection is the ability for a program to observe and therefore reason about its own state. Intercession is the ability for a program to modify its own execution state or alter its own interpretation or meaning. Both aspects require a mechanism for encoding execution state as data: providing such an encoding is called reification." [START_REF] Bobrow | CLOS in context -the shape of the design[END_REF] "A system having itself as application domain and that is causally connected with this domain can be qualified as a reflective system." [START_REF] Maes | Concepts and experiments in computational reflection[END_REF] A reflective language is a good system to implement new features since it is open and often malleable to changes [START_REF] Foote | Reflective facilities in Smalltalk-80[END_REF][START_REF] Kiczales | Efficient method dispatch in pcl[END_REF][START_REF] Rivard | Reflective Facilities in Smalltalk[END_REF]. However, there is a cost in the fact that a reflective system will reflect on its own implementation. This poses a problem since the implementors are not able to freely optimize the system without taking into account that users can have access via the reflective API to the underlying implementation. [START_REF] Kiczales | Issues in the design and documentation of class libraries[END_REF][START_REF] Kiczales | Efficient method dispatch in pcl[END_REF].

There is a plethora of approaches introducing new features in a language, but rarely do the authors evaluate the cost related to the presence of the underlying language reflective API [START_REF] Bracha | Mirrors: design principles for meta-level facilities of object-oriented programming languages[END_REF]. In this paper we present the lessons learned from introducing traits and breaking part of the structural MOP of Smalltalk.

Before illustrating the problem, we briefly present traits and also the reflective API of Smalltalk that got affected by the traits presence.

TRAITS IN A NUTSHELL

Since this paper shows how the optimized implementation of traits in Smalltalk broke part of the Smalltalk reflective API, we present traits and stress some particular design points.

Traits are units of behaviour. They are groups of methods that act as behavioural building block of classes [START_REF] Ducasse | Traits: A mechanism for fine-grained reuse[END_REF]. In addition to offering behaviour, traits also require methods, i.e., methods that are needed so that trait behaviour is fulfilled. Traits do not define state, instead they require accessor methods.

Figure 1 shows a class SyncStream that uses two traits, TSyn-cReadWrite and TStream. The trait TSyncReadWrite provides the methods syncRead, syncWrite and hash. It requires the methods read and write, and the two accessor methods lock and lock:. We use an extension to UML to represent traits (the right column lists required methods while the left one lists the provided methods). Explicit composition. A class is then defined by specifying its superclass, instance variables, and used traits. In addition it is the responsibility of the class to explicitly resolve conflicts that may occur when two traits define methods having the same name.

Trait composition is built around the following three rules:

• Methods defined in the composer take precedence over trait methods. This allows the methods defined in a composer to override methods with the same name provided by the used traits.

• In any class composer, the traits can be in principle in-lined to give an equivalent class definition that does not use traits.

• Composition order is irrelevant. All the traits have the same precedence, and hence conflicting trait methods must be explicitly disambiguated.

Conflict resolution. While composing traits, method conflicts may arise. A conflict arises if we combine two or more traits that provide identically named methods that do not originate from the same trait.

There are two strategies to resolve a conflict: by implementing a method at the level of the class that overrides the conflicting methods, or by excluding a method from all but one trait. In addition, traits allow method aliasing; this makes it possible to introduce an additional name for a method provided by a trait. The new name is used to obtain access to a method that would otherwise be unreachable because it has been overridden [START_REF] Ducasse | Traits: A mechanism for fine-grained reuse[END_REF].

In Figure 1, the class SyncStream is composed from TSyncRead-Write and TStream. The trait composition associated to SyncStream is:

TSyncReadWrite alias hashFromSync → hash + TStream alias hashFromStream → hash
The class SyncStream is composed of (i) the trait TSyncRead-Write for which the method hash is aliased to hashFromSync and (ii) the trait TStream for which the method hash is aliased to hash-FromStream.

Method composition operators.

Trait composition is based on four operators [START_REF] Ducasse | Traits: A mechanism for fine-grained reuse[END_REF]: sum (+), override ( ), exclusion (-) and aliasing (alias →). For further details on trait composition, we refer to the traits paper [START_REF] Ducasse | Traits: A mechanism for fine-grained reuse[END_REF].

Trait methods are then conceptually shared among all the classes using them. As we will see, sharing traits at the implementation level raises some problems in presence of the reflective API.

SMALLTALK REFLECTIVE API IN A NUTSHELL

Smalltalk offers a solid structurally reflective API [START_REF] Goldberg | Smalltalk 80: the Language and its Implementation[END_REF][START_REF] Rivard | Reflective Facilities in Smalltalk[END_REF] as well as some powerful behavioural capabilities [START_REF] Brant | Wrappers to the rescue[END_REF][START_REF] Ducasse | Evaluating message passing control techniques in Smalltalk[END_REF]. Here we present only the ones required to illustrate our point. Class as Objects. The programmer can introspect a class and get access to all its local method selectors as well as the inherited ones.

Point selectors

Returns all the selectors of methods locally defined

Point allSelectors

Returns all method selectors including the superclass ones Point #setX:setY: Returns a compiled method

Compiled methods as meta-objects. While in early Smalltalk implementations a compiled method was an internal object, it slowly acquired a reflective API in later versions. For example, a compiled method knows directly its class in Pharo, while before, all the system classes had to be queried. In addition, a compiled method knows its selector and has a property API to store additional state.

method selector

Returns the selector of the compiled method method methodClass Returns the class to which the compiled method belongs

The variable method in the table above holds a compiled method instance. A compiled method can be accessed by sending the message to a class with the method name as argument, for example,

Point

#setX:setY:.

PROBLEMS

In Squeak Smalltalk, like in most other Smalltalk dialects, a compiled method is installed in the method dictionary of a class. A compiled method is a regular object in memory and serves as a container for the bytecode of a method. The source is stored in external files. As described in Lienhard's Masters thesis [START_REF] Lienhard | Bootstrapping Traits[END_REF], the implementation of traits in the Squeak kernel takes the static approach where methods from traits are propagated to classes at compile time. (An alternative strategy would have been to modify the method lookup of the VM.)

In this first implementation of traits, we make use of the fact that compiled methods can be shared 1 . When a method is propagated from a trait to another trait or to a class, the very same method object is installed. This means, that method objects are shared between traits and classes. At the time we designed the new traits kernel, the sharing of compiled methods seemed like a reasonable design decision because it avoided to recreate a new compiled method for each class and hence saved memory.

While this optimization was good to optimize memory usage, in the presence of an introspective API it turned out to be a mistake. This became especially obvious when the API of the class CompiledMethod was later extended. For instance, querying for the class in which the method is installed or the method name by which the method can be called, became ambiguous for shared methods. With this design, a correct implementation of this API was not possible because storing the class and name of the method did not make sense for shared methods.

The Case of Aliases. Aliases in traits are not method renames as in Eiffel. An alias is just a new name for an existing method. Aliases are a good solution to let the programmer access a trait method that has been redefined in the class. Without aliases, the programmer would be forced to copy and paste the hidden method. Again with a space optimization in mind, aliases were implemented by adding an entry to method dictionary and by sharing the aliased method. This is a good solution for a non reflective language. The problem is that a compiled method can be asked for its name. It is not clear whether the aliased name or the hidden method name should be returned. Reflective API Problems. Besides the previous problems that can be solved by not sharing the compiled methods, there is a family of problems due to existing programs and tools not being aware of traits. For example, the code fileouter writes all the methods of a class to disk using the message selectors. Therefore, in presence of traits, the trait methods are saved as well mixed with the local methods. A similar problem occurred at the level of the user interfaces. Browsing a class did not distinguish between method acquired by trait composition and local or inherited ones. These problems were expected since the semantics of the method selectors changed and tools needed to be adapted to the presence of traits.

In summary, the sharing of method objects saved memory but hindered the evolution of a sound reflective API. Therefore, there was a need for a clear structural MOP that we will present in the following section.

A STRUCTURAL REFLECTIVE API FOR TRAITS

The new MOP is based on three parts: the absence of sharing between traits methods, a new API for navigating classes in presence of traits, and a compiled method and trait centered API.

Class and Traits

Common API of Classes and Traits. Classes and traits have a common interface as both are behaviors holding sets of methods. This means that both traits and classes can often be treated the same. Therefore, even though traits are a new concept, all tools working on classes can be easily adapted to take traits into account.

In the following, we present the common reflective API that is provided by both traits and classes.

traits

A collection of traits that are directly used allTraits

The transitive closure of all traits used selectors

The names of all methods installed in the method dictionary (i.e.

Non Sharing

The new implementation copies the method instances and their source code on a per use basis (for each class or trait). This way a compiled method has a unique selector and a unique class to which it belongs. The source is also not shared anymore since with aliases the selector of a method can differ from its original (in the old implementation, the source was patched before shown to the user). We propose a finer MOP for compiled method as shown below.

As we do not share the methods anymore, there will be a slight increase in instances of class CompiledMethod in the system. We show the increase for Pharo version #10337:

Number

New Compiled Method MOP

While just copying all the trait methods resolves the issues related to the lack of a one to one mapping between a name to access a metaobject and this metaobject, it does not really help clients to build (meta) applications in presence of traits. We present now that just adding one extra concept, the origin of a compiled method, results in a solid API. It should be noted that this solution was not that obvious at the beginning and that several iterations were necessary to arrive to this minimal yet expressive API.

Let us take the situation described by Figure 2. While abstract, it illustrates all the cases of trait usage, aliasing and local method definition. Compiled method methodClass. The message methodClass always returns the class in which the method is either local or used from a trait. methodClass therefore takes the standpoint where all traits are flattened into the classes that use them. If it is sent to a method from a trait, it returns the trait itself.

The following tests illustrate the semantics of methodClass when sent to traits. The traits Trait1 and Trait2 define two methods named c. By default a method defined in a trait answers its trait as its methodClass:

self assert: (Trait1>>#c) methodClass = Trait1. self assert: (Trait2>>#c) methodClass = Trait2.
This allows clients to treat traits and classes polymorphically from their method perspective. An alternative design would have been to return nil or raise an exception when sent to a trait.

The method c is redefined locally in the class C. The expression c methodClass returns C since the method is redefined locally. In addition c2 methodClass returns the same result, since the trait Trait2 is used by the class C. In presence of aliases as with the class D, which aliased the method c2 as c3, the methodClass is the class in which the alias occurs. Finally, even if a trait is composed of other traits (as Trait3 is composed of Trait2 in Figure 2), the methodClass of c2 in class A is A.

self assert: (C>>#c) methodClass = C. self assert: (C>>#c2) methodClass = C. c ^ 'Trait1>>c' c1 ^ 'Trait1>>c1' Trait1 c ^ 'Trait2>>c' c2 ^ 'Trait2>>c2' Trait2 c ^ 'Trait3>>c' c3 ^ 'Trait3>>c3' Trait3 A c ^ 'C>>c' C B -c D c3 -> c2
self assert: (Trait3>>#c) methodClass = Trait3. self assert: (A>>#c2) methodClass = A.

Compiled method selector. The selector of a compiled method is always the selector of its current use. In particular, an aliased method has the name of its alias name. Compiled method origin. Finally, in contrast to methodClass which always represents the current container of the receiver, we introduced the message origin which returns the defining class or trait of a compiled method. Such message makes the development of tools much simpler since the client does not have to navigate the internal trait composition structure. The origin of c2 in the trait Trait3 is Trait2 because it is not redefined in Trait3 (same for D #c2). Class A gets the method c from the Trait3 and not Trait2 which also defines it, since Trait3 locally redefined it. Since class B excluded method c from Trait2 and got the one from trait1. The origin of c is Trait1. Finally the origin of method c3 in class D which is an alias to the method c2 is the class Trait2.

Here even if the method C3 is only defined in D the API stresses its origin as a copy of c2. We may change this decision based on user feedback.

self assert: (B>>#c) origin = Trait1. self assert: (D>>#c3) origin = Trait2.

DISCUSSION

Introducing a reflective API for traits has shown a general problem of reflective systems: as the implementation serves both as the structure for execution and the structure for reflection, any implementation artefact will have an impact on reflection. Any optimization will be visible and can lead to problems like in the case of compiled methods sharing.

In this paper, we solved the problem by not optimizing compiled method sharing. But optimizations like these, especially concerning memory usage or execution performance, can be of great value to make a reflective system practical. Conversely, any definition of a MOP will put constraints on the implementation. Kiczales and Lamping [START_REF] Kiczales | Issues in the design and documentation of class libraries[END_REF] report their problems designing a MOP that would not constrain the implementors.

What we need is the ability to have multiple meta-models being available in the system. A low-level one concerned with execution and one or more higher-level models concerned with providing information for the programmer. By doing so we would be reifying the differences in behavior perceived throughout our analysis. Thus, providing different behavior for the different requirements.

Mirrors

Bracha and Ungar discuss three principles to build reflective APIs [START_REF] Bracha | Mirrors: design principles for meta-level facilities of object-oriented programming languages[END_REF]. Encapsulation: meta-level facilities must encapsulate their implementation. Stratification: meta-level facilities must be separated from base-level functionality. Ontological correspondence: the ontology of meta-level facilities should correspond to the ontology of the language they manipulate.

As a solution, Bracha and Ungar propose Mirrors. Reflective capabilities are provided by mirror objects. A solution with mirrorlike meta-objects is possible for the problem we encountered in this paper.

We may wonder why a compiled method which is a runtime object needs to know about its class and its selector. This question is valid and interesting. Especially when we see that a compiled method should be accessed via its class using a defined public API (A #foo or A compiledMethodFor: #foo).

Let's discuss an alternative design: suppose we introduce a separate compiled method metaobject in addition to compiled methods. In such a design, a compiled method does not act as a metaobject and it only holds information for playing its role; in such a case a compiled method does not know its class nor its selector and as such can be easily shared. Now since we still need a metaobject for compiled method, we introduce a dedicated class CompiledMethod-MetaObject, which holds a compiled method, its class and selector. The system should be adapted to return CompiledMethodMetaObjects instead of compiled methods. With such a design, it is interesting to see that the compiled method could be shared but not its metaobject. Now the question of the flow of API use is interesting. Indeed, a compiled method should not hold a pointer to its metaobject as the definition below illustrates. Else we can be in exactly the same situation we describe in the beginning of the paper.

CompiledMethod>>selector ^metaObject selector

Therefore compiled methods should only be manipulated through their metaobjects. This indicates a layering between the reflective API and its domain. Such a solution goes in the same direction of Mirrors where default Smalltalk reflective objects such as classes are separated into domain objects and their reflective counterpart.

Towards Multiple Meta-Models

The problem we encountered is actually very interesting for research: how can we put support for multiple meta-models into the reflective core of the language? Mirrors provide a first step towards solving this problem, but we think that more work is needed. For example, it is not clear how to manage the causal connection of the base-level with the multiple meta-models or how to model the connection between multiple meta-models.

CONCLUSION

We have presented a new reflective API concerned with the structure of classes and traits. The realization of this API showed a general problem of reflective systems: the implementation serves as the model for reflection and therefore all implementation-level artefacts are visible for reflection.

We solved the problem by not optimizing for space (not sharing compiled methods) and presented the new reflective API.

The problem presented in this paper is not new. However, while a large body of work introduces new concepts in current reflective languages, really few were concerned about the impact of such introduction and the difficulties faced because of the presence of reflective API.

Beyond the direct scope of the paper, the problems show that more research is needed to make it possible to have multiple metamodels to be able to represent both an optimized model for execution and high-level models for the programmer.

Figure 1 :

 1 Figure 1: The class SyncStream is composed of the two traits TSyncReadWrite and TStream.

Figure 2 :

 2 Figure 2: Covering all situations with traits and classes.

  testSelector self assert: (A>>#c) selector = #c. self assert: (C>>#c) selector = #c. self assert: (Trait3>>#c) selector = #c. self assert: (Trait3>>#c2) selector = #c2. self assert: (D>>#c3) selector = #c3.

  self assert: (Trait3>>#c2) origin = Trait2. self assert: (A>>#c2) origin = Trait2. self assert: (D>>#c2) origin = Trait2. self assert: (A>>#c) origin = Trait3. Method c is defined locally in class C, which overrides its definition from Trait2. Its origin is then such a class C. self assert: (C>>#c) origin = C.

One exception are methods with a super message send since they capture the static superclass at which the lookup is started.

Acknowledgments. We gratefully acknowledge the financial support of the Swiss National Science Foundation for the projects "Bringing Models Closer to Code" (SNF Project No. 200020-121594, Oct. 2008 -Sept. 2010) and "Biologically inspired Languages for Eternal Systems" (SNF Project No. PBBEP2-125605, Apr. 2009 -Mar. 2010).

We thank Jorge Ressia for discussing the concepts presented and his help in reviewing various drafts of this paper.


Marcus Denker

Stéphane Ducasse

Philippe Marschall

Sub-Method Reflection

Reflection has proved to be a powerful feature to support the design of development environments and to extend languages. However, the granularity of structural reflection stops at the method level. This is a problem since without sub-method reflection developers have to duplicate efforts, for example to introduce transparently pluggable type-checkers or fine-grained profilers. In this paper we present Persephone, an efficient implementation of a sub-method meta-object protocol (MOP) based on AST annotations and dual methods (a compiled method and its meta-object) that reconcile AST expressiveness with bytecode execution. We validate the MOP by presenting TreeNurse, a method instrumentation framework and TypePlug, an optional, pluggable type system which is based on Persephone.

the execution. However, structural reflection often stops at the method level. For example in Smalltalk (the same holds in Java) the only entities supporting limited sub-method structural reflection are collection of bytes (from the compiled method) or characters (from the textual representation of method bodies).

The lack of a higher abstraction of method source code hampers the implementation of tools that require to reflect about it. Examples of such tools are code transformers (e.g., a refactoring tool), pluggable type-checkers, or fine-grained code coverage analysis tools. Since programming languages do not provide support for sub-method reflection each tool has to resort to implement its own infrastructure to represent and reason about source code.

However, this is difficult because (1) text is definitively not structured enough, (2) bytecode is low-level and (3) abstract syntax trees and text are not causally connected to the low-level representation of methods (i.e., changes are not reflected in the bytecode representation of methods).

Another problem is that it is hard to reuse existing infrastructure of one tool in another tool because solutions tend to be very specific. A common infrastructure would not only allow for code reuse but also facilitate data and cache sharing and provide a layer for communication between the tools.

In this paper we propose reflective methods which provide a high-level, extensible representation of method bodies accessible through a meta-object protocol (MOP). The high-level representation used by tools is causally connected to its low-level bytecode representation used by the virtual machine (VM). That is, changes to the high-level representation are automatically reflected at the bytecode level. While our implementation is done in Squeak [22], we believe that the concepts can be applied to other languages without severe changes.

The contribution of this paper is: a motivation for sub-method reflection, an analysis of the problems to support it, a MOP for sub-method reflection based on annotated abstract syntax trees and an efficient implementation named Persephone. We validate our approach on two non-trivial tools which are implemented based on Persephone. This paper is organized as follows. In Section 2 we analyze the difficulties for supporting sub-method reflection. Section 3 presents our solution. After showing case studies (Section 4 and 5) we discuss benchmarks (Section 6) to validate the implementation. After a short overview of related work (Section 7) we conclude in Section 8.

CHALLENGES FOR SUPPORTING SUB-METHOD REFLECTION

To support the need of different tools such as a code browser, a code coverage tool or a refactoring engine, we need a representation that is extensible, it should allow tools to annotate the structural elements with metadata and the use of metadata for

276

JOURNAL OF OBJECT TECHNOLOGY VOL 6, NO. 9 communication between tools. The representation should be persistent: it should not be needed to re-generate it for every tool. We need a high-level model of the method that supports powerful code transformation. Finally, the representation should be causally connected: changing the representation should change the system with no need of explicitly calling a compiler.

Most of the time, modern OO languages provide two representations for the method level: the text that the programmer typed, and bytecode, the language that the virtual machine interprets. Internally, tools that need sub-method structure do not use the text nor the bytecode directly, but generate a custom representation, in many cases an Abstract Syntax Tree (AST). We now evaluate the three representations as a foundation to support a sub-method MOP.

INTRODUCTION

Reflection [START_REF] Cantwell | Reflection and semantics in a procedural language[END_REF] has proved to be an important property of long-lived and highlydynamic systems. The literature is full of examples of uses for reflection. For instance, message passing control has been used for a wide range of application analysis approaches, such as tracing [START_REF] Herczeg | What tracers are made of[END_REF][START_REF] Brant | Wrappers to the rescue[END_REF], automatic construction of interaction diagrams, class affinity graphs, test coverage, as well as new debugging approaches [START_REF] Lewis | Using events to debug Java programs backwards in time[END_REF]. Message passing control has also been used to introduce new language features in several languages, for instance multiple inheritance [START_REF] Borning | Multiple inheritance in Smalltalk-80[END_REF], distribution [START_REF] Bennett | The design and implementation of distributed Smalltalk[END_REF][START_REF] Paul | Transparent forwarding: First steps[END_REF], instancebased programming [START_REF] Beck | Instance specific behavior: Digitalk implementation and the deep meaning of it all[END_REF], active objects [START_REF] Briot | Actalk: A testbed for classifying and designing actor languages in the Smalltalk-80 environment[END_REF], concurrent objects [START_REF] Yokote | Experience and evolution of ConcurrentSmalltalk[END_REF], futures [START_REF] Pascoe | Encapsulators: A new software paradigm in Smalltalk-80[END_REF] and atomic messages [START_REF] Foote | Reflective facilities in Smalltalk-80[END_REF][START_REF] Mcaffer | Meta-level programming with coda[END_REF], as well as backtracking facilities [START_REF] Wilf | Building a backtracking facility in Smalltalk without kernel support[END_REF]. Nowadays, AOP is often using reflection to support its implementation [START_REF] Tanter | A versatile kernel for multi-language AOP[END_REF][START_REF] Tanter | An extensible kernel language for AOP[END_REF].

Current object-oriented languages provide access to program structure reifications. In most cases (e.g., Java or C#) only introspection is supported (i.e., these descriptions can be queried). In other languages, intercession is also supported (i.e., the program itself can be changed) [START_REF] Bobrow | CLOS in context -the shape of the design[END_REF]. Dynamic languages usually support both introspection and intercession (CLOS [START_REF] Kiczales | The Art of the Metaobject Protocol[END_REF], Ruby, Python, Smalltalk [START_REF] Foote | Reflective facilities in Smalltalk-80[END_REF][START_REF] Ducasse | Evaluating message passing control techniques in Smalltalk[END_REF]). In such languages, the representation of the program is causally connected to the system itself [START_REF] Maes | Concepts and experiments in computational reflection[END_REF]. When the representation is changed, the running application changes, too and conversely.

The literature also distinguishes structural and behavioral reflection [START_REF] Kiczales | The Art of the Metaobject Protocol[END_REF]: structural reflection deals with program structure while behavioral reflection deals with Cite this document as follows: Marcus Denker, Stéphane Ducasse, Andrian Lienhard, Philippe Marschall: "Sub-Method Reflection", in Journal of Object Technology, vol. 6. no. 9, Special Issue. TOOLS EUROPE 2007, October 2007, pp. 275-295 http://www.jot.fm/issues/ issue_2007_10/paper14/

Text as Sub-Method Representation

In all current major programming languages, the programmer types text [START_REF] Dimitriev | Language oriented programming: The next programming paradigm[END_REF][START_REF] Edwards | Subtext: uncovering the simplicity of programming[END_REF]. This text is then used by the compiler to generate executable code. Text itself has no real structure -it is just a collection of characters. Thus text as a sub-method representation has the following problems: Low-level. Text does not provide any high-level interface: it lacks the possibility to scope information and manipulate the underlying program elements.

Not causally connected. Changing the method body text has no effect. We need to call the compiler to generate a method.

Therefore text is almost never used directly for analysis and manipulation. Instead, tools parse the text into an intermediate format such as an AST.

AST as Sub-Method Representation

A commonly used and generated intermediate representation is the AST. For example, ASTs are used by the compiling chain and refactoring engine. Using the AST as sub-method representation has the following properties:

Not persistent. Representations such as ASTs are not persistent: they are generated and then used, but not stored. While trees can be created from source code, the meta-information, which we would like to associate to specific nodes has to be stored in separate structures.

Not causally connected. Changing an AST does not have an immediate effect on the underlying run-time behavior. Depending on the compiler API, the AST or the method body text is passed to the compiler, which will compile and install the method in its class.

Bytecode as Sub-Method Representation

In contrast to the other representations, bytecode is causally connected: changing bytecode directly changes the behavior of the system. Bytecode has been used, in both Java and Smalltalk, for a form of structural reflection (Javassist [START_REF] Chiba | An easy-to-use toolkit for efficient Java bytecode translators[END_REF] and Byte-Surgeon [START_REF] Denker | Runtime bytecode transformation for Smalltalk[END_REF]) 1 . Both provide a high-level interface to the user, e.g., by abstracting away bytecode details such as the different encodings for message sending or providing a way to specify code to be inlined as a string in the host language. Nevertheless, in the end programmers are forced to deal with bytecode level abstractions which may be different than the programming language the methods are written in [START_REF] Bracha | Mirrors: design principles for meta-level facilities of object-oriented programming languages[END_REF]. Bytecode as a model for sub-method structure has a number of problems:

Different representation needs. There is a dilemma: on the one hand the execution engine (bytecode VM) requires bytecode for execution, and on the other hand programming environments or programmers require text or an abstract and high-level representation of the source code.

Low level abstraction. The programmer has to deal with the idiosyncrasies of the bytecode representation. For example, control structures may be optimized as it is the case in Smalltalk bytecode. In addition, there is a mismatch between the program level abstractions and its runtime [START_REF] Bracha | Mirrors: design principles for meta-level facilities of object-oriented programming languages[END_REF].

All three discussed representations have the problem of missing extensibility and lack a way to describe metadata: Missing extensibility. Tools cannot easily extend the representation for their needs. Thus, they often use a custom representation which leads to a situation where tools cannot easily share meta-information, e.g., when one tool gathers information for others to use [START_REF] Vainsencher | A pattern language for extensible program representation[END_REF][START_REF] Meyers | Difficulties in integrating multiview development systems[END_REF].

Mixed base-level and meta-level code. Code transformation (e.g., bytecode instrumentation [START_REF] Chiba | An easy-to-use toolkit for efficient Java bytecode translators[END_REF]) is often used to reflect on method execution: small snippets of code, so called hooks, are inserted into the original code. This leads to the problem of distinguishing code of the original method from the code added by the instrumentation framework.

Requirements

We want the best of both worlds: AST and bytecode representations. In summary, the representation should be (i) causally connected and well integrated in the system, (ii) persistent, (iii) extensible and (iv) reasonably compact with minimal performance impact. Furthermore, it should support the separation of base and annotated code and offer a high-level abstraction to the developers.

Such a model for reflective methods would make it easier to develop and deploy a new generation of tools that work on sub-method elements.

We have seen that bytecode is too low level for our purpose, the AST could be useful. But we need to take practical consideration into account: can such a high-level representation be reasonably compact and efficient? In the next section we present a solution that satisfies these constraints.

REFLECTIVE METHODS: ANNOTATED AND CACHED ASTS

Our solution is based on a dual representation of methods which combines an ASTbased representation offering high-level manipulations with a compact bytecodeoriented representation supporting fast execution. The abstract syntax trees can be annotated, the semantics of annotation is defined by specializing dedicated compiler plugins. The causal connection and efficient execution is ensured by dual methods that recompile bytecode automatically from their associated and annotated AST. In such a context, we define three meta-objects: (1) the ASTs and their associated transformation API, (2) the annotations and their semantic definition specified by [START_REF] Bergel | Classboxes: Controlling visibility of class extensions[END_REF]

Dual Methods

We implemented the presented approach in Squeak, an open-source Smalltalk. In Smalltalk, code is compiled to bytecode which resides in compiled method objects (an instance of the class CompiledMethod [START_REF] Goldberg | Smalltalk-80: The Language[END_REF]). Besides bytecode, a compiled method keeps a pointer to its source and a dictionary for additional state that is associated with a method (e.g., its name). We enhanced the compiler to generate a reflective method instead of a compiled method. A reflective method provides access to its AST meta-object. Before its first execution, a compiled method is generated from the reflective method. Such a compiled method is cached to minimize performance loss. When the code of a method is changed the cache is flushed and the reflective method is reinstalled in the method dictionary as shown by Figure 1 and described in [START_REF] Marschall | Persephone: Taking Smalltalk reflection to the sub-method level[END_REF].

The system was named after Persephone,the greek goddess who spends half of her time in the underworld and the other half in the upper world. Like Persephone, methods are seen sometime as part the underworld (the virtual machine), other time as part of the upper world of high-level abstraction.

Before going into the details of our sub-method protocol we present an example of annotations that are visible in the source code. We show later that some annotations may be invisible in method source code.

A Simple Example: Compile-Time Evaluated Expressions

The following piece of code is the definition in Smalltalk of the method calcu-lateNinePower which evaluates and returns the result of 9 to the power 10000. Without the annotation (<:evaluateAtCompiletime :>) the execution of such a method will at runtime send the message raisedTo: to the object 9 and then return 9 10000 . Using annotations we can mark any abstract syntax tree element, i.e., any expression. Here we specify that the expression should be executed at compile-time. EvaluateAtCompiletimeAnnotation is a subclass of NoValueAnnotation since this annotation does not expect arguments. The class implements one method: key that returns the annotation name (here the symbol evaluteAtCompiletime).

280

JOURNAL OF OBJECT TECHNOLOGY VOL 6, NO. 9

The compiler plugin class, named CompiletimeEvaluator, is a subclass of Compiler-Plugin. Besides that, it only has two small methods visitNode: and evaluateNow:. We check every node for the evaluteAtCompiletime annotation. If the annotation is present, we pass the node to evaluateNow: which does the evaluation. If the annotations is not set, we just continue the visiting process. The method evaluateNow: evaluates an expression by sending the evaluate message, a literal node is created to hold the result. It replaces the original node and the visiting process continues.

AST and Tree Transformation API

The AST used in Persephone is an extended version of the Smalltalk refactoring engine AST [START_REF] Roberts | An automated refactoring tool[END_REF]. We extended it to provide annotations for all node objects. Reflective methods provide a comprehensive abstract syntax tree API. Trees are easily edited and transformed (added/removed/replaced, see Table 1). On top of these simple transformations, the Parse-TreeRewriter is a rewrite engine which allows transformations to be specified at a high-level of abstraction. It should be noted that this API provides a way to destructively transform a tree: after the transformation, the tree is changed in the same way as if we would have edited text and recompiled the method. This is useful e.g., for refactorings. However, destructively changing a tree is not always what we need. As we will present in Section 4, annotations provide a way to define non destructive transformations i.e., we can always identify the original method.

Node

AST Annotations

As discussed in Section 2, reflective methods should serve as the main method representation for many different usages. A consequence is that users need to be able to add information about objects (nodes) directly to those objects themselves. Adding behavior to the node classes is possible using the class extension mechanism of Smalltalk which allows a package to define methods on classes defined in another package [START_REF] Bergel | Classboxes: Controlling visibility of class extensions[END_REF]. For object state extensions, our MOP provides annotation objects that are attached to any node as shown in Figure 3. Each annotation is uniquely identified by a key and each node has a dictionary that maps keys to annotations. Annotations are instances of one of the three subclasses of Annotation. They can have zero, one or multiple values. To define a custom annotation we subclass from the appropriate subclass depending on their number of values (see Figure 3). The difference between multi-valued and single valued annotations is that the former can be defined multiple times on the same expression with different values whereas the latter cannot.

The expression anExpression <:aSelector: anArgument:> attaches an annotation with one argument to the expression anExpression. Annotations are supported on all expressions and additionally on method arguments, block arguments and each variable name in a temporary variable definition.

The argument of an annotation can be any Smalltalk expression. In the simplest case, when the argument is just a literal object, the value of the annotation is set to this literal object. When the argument is an expression, the value of the annotation is the AST of this expression. We can specify when this AST is evaluated, either at compile time or later at run-time. In addition we provide a reflective interface for annotations which can be queried and set at runtime. Annotations may or may not appear in the source code. For example, an invisible annotation is the number of times a particular tree element has been executed; an example for a visible annotation is a type declaration. Non-textual annotations can be added reflectively to any node at runtime. These annotations are kept as long as the AST is not

282

JOURNAL OF OBJECT TECHNOLOGY VOL 6, NO. 9 regenerated. For example, when a method is recompiled from source, the nodes of this method will have no annotations, but clients can be notified of any code change and add annotations again if needed.

Annotation Semantics

Without specific interpretation, annotations are pure metadata: they have no predefined semantics. To specify annotation semantics, Persephone defines a meta-object protocol for the compiler and bytecode generator.

The MOP is based on a plugin architecture. Before generating any code, the compiler framework copies the AST and then calls all defined compiler plugins by priority order. A compiler plugin is just a subclass of CompilerPlugin. Plugins affect compilation by transforming the AST. As we provide fully reflective access to the annotations, the compiler plugin may take annotations into account. We present a full example in Section 4 with the instrumentation framework TreeNurse.

Characteristics of the Solution

Now we analyze our approach according to the requirements presented in Section 2.

Causal connection. The implementation ensures that the executed bytecode is always in sync with the reflective method. The whole mechanism is completely transparent to the user. Thus, we provide a causally connected and integrated model.

Persistency. Reflective methods (the AST including the annotations) are installed in the method dictionary of a class. As reflective methods are normal Smalltalk objects, they are written to the disk when the system is stopped. Thus the model is persistent.

Abstraction level. We reuse the same representation and API as the Smalltalk refactoring engine which has proven to be a usable abstraction for various analyses such as refactoring and general meta-programming.

Separation of base and meta-level code. The annotation framework provides a way to structure code into data (AST) and metadata (annotations). For example, instrumentations as the results of the meta-programs are still completely identifiable since they are represented in the AST as annotations.

Extensibility. Annotations provide a convenient way to associate metadata with the AST nodes for storing additional state. Due to the compiler MOP, we can use annotations to extend the language semantics.

Size and performance We will discuss memory consumption in Section 6. The provided caching scheme ensures that at runtime we generate a compiled method only once.

In situations where even this relatively low overhead is too large, we can statically generate all bytecode before deployment.

We now validate our approach by presenting two tools that we have built based on Persephone. The first tool is a framework for instrumenting code (TreeNurse). Tree-Nurse is very similar ByteSurgeon [START_REF] Denker | Runtime bytecode transformation for Smalltalk[END_REF] or Javassist [START_REF] Chiba | An easy-to-use toolkit for efficient Java bytecode translators[END_REF]. Using TreeNurse, we show how the extensibility of our approach is used for communication between tools: we implement a statement coverage tool that records the coverage information as annotations on the reflective method. The second example presents TypePlug, an optional type system that uses textual annotations.

VALIDATION: INSTRUMENTATION USING ANNOTATIONS

TreeNurse is a framework for instrumenting code. It is modeled to have an interface very similar to ByteSurgeon, a bytecode transformation framework [START_REF] Denker | Runtime bytecode transformation for Smalltalk[END_REF]. In contrast to ByteSurgeon, it works on the AST and is implemented as a Persephone compiler-plugin.

To present TreeNurse, we start with discussing a simple example. We have a method that does an assignment and we want to annotate the code with trace statements for debugging that increases a counter. The original code and what we want to actually do at runtime are shown in Figure 4.

Original Code

Instrumented Code a := 1 max: 3 a := 1 max: 3. AssignmentCounter inc. Here it is important to understand that we want to change the semantics of the method, but we don't want to change the code itself. The counter is not part of the design of the system, it is just a temporal addition for debugging. If we transform the code with the help of the refactoring API of the AST, the result would be a new method where the debugging code would be undistinguishable from the original code. Bytecode instrumentation frameworks such as ByteSurgeon or Javassist have the same problem: once the bytecode is transformed, we do not know which statements are part of the original method and which have been added, except at the price of tedious bookkeeping.

An instrumentation framework built on top of Persephone does better: we use annotations to store the instrumentations in the reflective method and they are taken into account when generating code, as shown in Figure 5.

The actual code to do this annotation with our framework looks like this: This code adds an after annotation to all assignments in a method with the effect of incrementing the counter. We instrument the method by sending instrument: passing a

284

JOURNAL OF OBJECT TECHNOLOGY VOL 6, NO. 9 block with the instrumentation code. We only want to instrument assignments so we select them by sending isAssignment to each node passed to the block.

By implementing TreeNurse using reflective methods, TreeNurse has the following unique properties:

• It works on AST nodes and not binary code.

• The instrumentation is stored as an annotation on the AST node. The original AST is left untouched.

• The generation of bytecode is done lazily, on need. Instrumentation merely results in the bytecode cache to be reset. This can lead to considerable time savings especially for large programs.

The Instrumentation API

On all the AST nodes, we support the following transformation API:

Operation Description insertBefore: Annotate to insert code before this node insertAfter: Annotate to insert code after this node replace:

Annotate to replace the node by the code For an easy selection of the nodes to be instrumented, we provide dedicated iteration interface, for example instrumentAssignments: iterates over all assignment nodes.

Meta Variables

The only variables that can be directly referenced from inside the block are self, super and thisContext, as the code of the block is to be inlined into the instrumented method. Thus, these variables will be bound to their values at runtime, not at instrumentation time. For everything else, block arguments have to be used. Each kind of node provides a different set of meta variables that can be used as block arguments:

Node

Metavariable Description any node node the node itself message node receiver the receiver of the message arguments a collection of all arguments selector the selector of the message method node arguments a collection of all arguments selector the selector of the method assignment node variable the variable to be assigned a new value variableName the name of the variable value the value to be assigned to the variable return node value the value to be returned variable node value the value of the variable literal node value the value of the literal

The following code gives an example of a replace annotation, which uses two meta variables, one referencing the variable and the other the value of the assignment: TreeNurse replaces all assignments with a new assignment that adds the number 3000 to the value expression of the original code.

Example: Code Coverage Analysis

Code coverage analysis per expression is a conceptually simple task. Before an expression gets executed it is marked as executed. After the program is run the executed expressions are printed differently from the ones that were not executed. The most convenient way to store information about a node is by adding an annotation to it that holds the information. To keep track of how many times a node has been executed we create a subclass of SingleValuedAnnotation named ExecutedAnnotation. We then add a method markExecuted to ProgramNode via a class extension:

Node>>markExecuted (self annotationAt: ExecutedAnnotation key) increment

If we now send markExecuted to any node, its execution count will be incremented, which we can do using our instrumentation framework: Here we iterate over all the nodes of the method, the block is evaluated for each node, binding the node to the variable eachNode. Before each node, we insert code. The inserted code is described by a block. This block references the meta variable node which references the AST node at runtime.

Here we see a usage of the node meta variable. It reifies the node, so we can call the markExecuted directly on the AST nodes. To produce the final output a pretty printer presents the status of execution.

VALIDATION: PLUGGABLE TYPE SYSTEM

TypePlug [START_REF] Haldimann | Typeplug -pluggable type systems for smalltalk[END_REF] is an optional, pluggable type system [START_REF] Bracha | Pluggable type systems[END_REF] for Squeak. It consists of a type reconstructor and inferencer that is used by a type checker to check Squeak programs for type correctness.

A type checker is an example for a program that works on code: we need to be able to attach metadata to expressions (the types) and be able to reason about this metadata. Thus the realization of a type system validates especially the extensibility of the reflective methods. We need to be able to model metadata that describes the type of any expression in the code.

With Persephone, types are represented as annotations on nodes in the AST. They can be declared on method and block arguments, method and block return values and temporary variable declarations. There are two different ways to declare types.

1. Using a special code browser to annotate the nodes. This has the advantage that types are declared without changing the source code but can still be checked into a source code management system. This is the preferred way for typing existing code especially system classes like Boolean or Collection.

2. Textual annotations, which are placed in the source code.

The following code shows a method annotated with types: bitFromBoolean: aBoolean <:type: Boolean :> ^(aBoolean ifTrue: [START_REF] Beck | Instance specific behavior: Digitalk implementation and the deep meaning of it all[END_REF] ifFalse: [0]) <:type: Integer :>

The method takes a boolean as an argument and returns an integer.

The TypePlug case study shows that our representation provides the extensibility needed for extending the language with a pluggable type system. TypePlug demonstrates the usefulness of providing textual annotations where the annotation itself is not limited to be a static predefined datatype. Persephone supports annotations that contain general Smalltalk code. The evaluation of this code can be completely controlled by the annotation class itself or the compiler plugin. This allows for building complex annotations as required e.g., for advanced type systems.

PERFORMANCE AND MEMORY ANALYSIS

In this section we discuss results of performance benchmarks and memory consumption. The machine used is an Apple MacBook Pro (2Ghz Intel Core Duo, 2GB RAM). We present an analysis of the effect of our caching scheme and discuss memory consumption.

Performance of Cache

To analyze cache performance, we use the TinyBenchmark suite that is part of the normal Squeak distribution (Table 2). The TinyBenchmark suite tests bytecode interpretation and message send performance. For this test, we use the runtime of the benchmarks for assessing cache performance. First, we record the runtime for an unmodified Squeak. Then we run the benchmark with Persephone in two cases: with and without caching the generated bytecode.

When running the benchmark with caching disabled, the system gets too slow to be usable as bytecode needs to be generated for each method execution. We had to abort the benchmark run after one hour. We see a noticeable speedup as soon as we turn on caching: Persephone shows no detectable slowdown compared to standard Squeak, even though bytecode had to be generated for the benchmark methods on the first execution.

Caching Scheme runtime unmodified Squeak 6.9 seconds Persephone, no cache >1 hour Persephone, cache 6.9 seconds Table 2: The effect of method caching.

Thus we can see that the cache provides a substantial speedup and enables a system using reflective method to be as fast as the standard Squeak system.

Memory Considerations

Representing a method as an AST in which each node is an object obviously requires a lot more memory than its corresponding compiled method which only stores the bytecodes. Table 3 shows the memory consumption of the AST of a complete Squeak system.

Name

number classes memory Squeak 3.9 2244 20 MB Squeak 3.9 AST 2244 123 MB Table 3: Memory consumption.

We see that the system uses a lot of memory, but in a typical development scenario, the system is already usable as is without any further optimization. In addition to that,

288

JOURNAL OF OBJECT TECHNOLOGY VOL 6, NO. 9

reflective methods are mostly only reified for parts of the system, for example a single package that needs to be analyzed.

To assess if the size is practically usable, we compare to the size of code loaded into Eclipse, a development environment used widely in industry. We took the source of Ar-goUML2 and loaded it in Eclipse version 3.2. ArgoUML Version 0.24 consists of ca. 1300 classes, it is thus much smaller then Squeak. Eclipse allocates ca. 180MB of memory when having the ArgoUML source loaded. Thus the memory consumption we see for Persephone is typical for a modern development environment.

It is also to note that those figures have to be considered as upper bounds since we

have not yet fully applied some memory related optimizations. For example, the size of the AST data can be optimized further by not referencing scanner-token data. In addition to that, we plan to experiment with AST specific compression techniques [START_REF] Franz | Slim binaries[END_REF].

RELATED WORK

Mirrors

Bracha and Ungar [START_REF] Bracha | Mirrors: design principles for meta-level facilities of object-oriented programming languages[END_REF] suggest structuring the meta layer with the help of mirrors. In this approach, objects are not reflective, they need a mirror that provides the reflective API. They mention mirrors on methods as future work. Mirror methods and our reflective methods share some similarities, but as mirrors are generated on demand they are not persistent. They provide a high-level view on low-level objects. We instead provide the high level view by default.

Annotations

Annotations are not new. For example, Javadoc tags are a form of annotation. More recently, Java 1.5 adds support for annotations to the language. As of release 5.0, Java has a general purpose annotation (also known as metadata) facility that permits you to define and use your own annotation types. The facility consists of a syntax for declaring annotation types, a syntax for annotating declarations, APIs for reading annotations, a class file representation for annotations, and an annotation processing tool. Java 1.5 annotations are only allowed for type, field, variable and method declarations and are not allowed on type parameters or method invocations. VisualWorks and Squeak also support method annotations (also called pragmas).

Spoon [START_REF] Pawlak | Spoon: annotation-driven program transformation -the aop case[END_REF] is an open compiler for Java that provides compile-time reflection. With Spoon, the Java AST can be transformed before it is compiled to bytecode. The processors of Spoon are similar to the compiler-plugins of Persephone. Spoon provides support for Java annotations, i.e., the transformation processors can read annotations. The main difference to our work is that Spoon works at compile-time, not runtime. The AST is compiled to bytecode and not available at runtime. Spoon provides an annotation-aware compile-time transformation framework, but not causally connected structural reflection at runtime. In addition to that, it is restricted to the annotation model provided by Java.

Higher Level Abstraction: Beyond Text

There have been a number of proposals over the years to move away from text as the only representation of code. Dimitriev [START_REF] Dimitriev | Language oriented programming: The next programming paradigm[END_REF] argues that programs should no longer be text but a graph described with a metamodel built for a certain kind of problem. The language would be mapped to another one for execution or interpretation. Edwards [START_REF] Edwards | Subtext: uncovering the simplicity of programming[END_REF] argues that programs should no longer be text and the representation of a program should be the same as its execution. His programs are trees created by copying. He also identifies the need to customize the presentation of a program. Black [START_REF] Black | Perspectives on software[END_REF] makes a case to free programs from their linear structure and replace them with a much richer abstract program structure (APS) that captures all of the semantics, but is independent of any syntax. Conventional one and two dimensional syntax, abstract syntax trees, class diagrams, and other common representations of a program are all different "views" on this rich abstraction. None of these proposals discuss the use of the high-level representation for reflection.

Sub-method Structure

In LISP [START_REF] Mccarthy | Recursive functions of symbolic expressions and their computation by machine, part I[END_REF] source code is itself made up of lists. As a result macros can manipulate it using the list-processing functions available in the language. This functionality is limited to macros at compile time and cannot be applied to functions at runtime.

In IO [START_REF] Dekorte | Io: a small programming language[END_REF] code is a runtime inspectable and modifiable tree. Message arguments are passed as expressions and evaluated by the receiver. Selective evaluation of arguments can be used to implement control flow. IO does not yet provide a way to extend the representation.

Sub-Method Behavioral Reflection and AOP

Behavioral Reflection is concerned with the execution of programs [START_REF] Cantwell | Reflection and semantics in a procedural language[END_REF]. The elements reasoned about can be those of sub-method abstraction, like message sends and instance variable accesses. Reflection frameworks like Reflex [START_REF] Tanter | Partial behavioral reflection: Spatial and temporal selection of reification[END_REF] or Geppetto [START_REF] Röthlisberger | Unanticipated partial behavioral reflection[END_REF] provide a static model for sub-method structure, modeled as a sequence of operations. Thus they provide less complete structural model then that of the AST. For implementation, they rely on bytecode instrumentation.

In AOP [START_REF] Kiczales | Aspect-Oriented Programming[END_REF], a joinpoint describes a point in the the execution of a program. Thus AOP deals with an execution model, it does not provide a general, static model of code. The structural model provided by Persephone in connection with partial behavioral reflection would be an interesting basis for implementing an AOP system.

CONCLUSION AND FUTURE WORK

In this paper we have motivated the need for reflective methods and presented an implementation called Persephone. We validated our approach by implementing an instrumentation framework and a pluggable type system.

Persephone has been used very successfully in a number of projects. The Tree-Nurse instrumentation framework has been used to implement a test coverage tool that provides line-by-line coverage analysis. Additionally, the instrumentation framework is the 290 JOURNAL OF OBJECT TECHNOLOGY VOL 6, NO. 9

basis of an ongoing experiment that analyzes how objects flow through the system. For this, TreeNurse is used to instrument all variable assignments and accesses and message sends.

We plan to explore the use of annotations in the context of behavioral reflection. Geppetto [START_REF] Röthlisberger | Unanticipated partial behavioral reflection[END_REF] has been implemented using bytecode instrumentation. We are evaluating how Geppetto can use annotations on the AST instead of hooks at the bytecode level.

Compactness of the representation is an interesting field for future work. For our experiments, memory consumption has never been a problem, but nevertheless, we plan to research how to improve storage by optimizing the AST representation leveraging transparent AST compression.

Another interesting direction is to use reflective methods to replace text: source code could be reconstructed from the AST. The current version provides some support for recovering formatting, but it needs to be improved and integrated with the tools. Adrian Lienhard is a doctoral candidate in computer science at the University of Bern and co-founder of netstyle.ch, a startup company specialized in business Web application development. His research interests include reengineering of object-oriented systems, dynamic analysis, dynamic languages, language design, program visualization and Web applications. He received his MSc in computer science from the University of Bern. Contact him at lienhard@iam.unibe.ch.

Philippe Marschall is Software Engineer at Netcetera, a swiss IT services company. He received his MSc in computer science from the University of Bern. Contact him at philippe.marschall@gmail.com.

VOL 6, NO. 9 JOURNAL OF OBJECT TECHNOLOGY 295

calculateNinePower ^( 9 Figure 2 :

 92 Figure 2: Extension for compile-time evaluation.

  visitNode: aNode ^(aNode hasAnnotation: EvaluateAtCompiletimeAnnotation key) ifTrue: [ self evaluateNow: aNode ] ifFalse: [ super visitNode: aNode ] evaluateNow: aNode | value literalNode | value := aNode evaluate. literalNode := LiteralNode value: value. aNode replaceWith: literalNode. ^self visitNode: literalNode

Figure 3 :

 3 Figure 3: The annotation hierarchy.

Figure 4 :

 4 Figure 4: Code instrumented with a counter.

  method instrument: [ :node | node isAssignment ifTrue: [ node insertAfter: [ AssignmentCounter inc ] ] ].

Figure 5 :

 5 Figure 5: Instrumenting a reflective method with annotations.

  method instrumentAssignments: [ :node | node replace: [ :variable :value | variable := value + 3000 ]

  method instrument: [ :eachNode | eachNode insertBefore: [ :node | node markExecuted ] ].

  the plugins. This set of objects enables what we call a reflective method.

	:ReflectiveMethod		:CompiledMethod
	annotation	compiledMethod
			#(12 13 45 38 98 128
	annotation	reflectiveMethod	84 72 42 77 22 28 59
			32 7 49 51 87 64)
	Tools		VM
	Figure 1: A reflective method is the meta-object of a compiled method.

Table 1 :

 1 The transformation API for nodes.

		Operation	Description
	any node	replaceWith:	replace this node
		removeNode:	remove a node
		replaceNode:withNodes: replace one node with a collection

replaceNode:withNode: replace a direct child node sequence node addNode:

append a node at the end addNodeFirst:

insert in front addNode:after:

insert after a given node addNode:before:

insert before a given node

In the case of Javassist for Java it should be noted that it can only provide load-time structural reflection: newly loaded code can be transformed, but once it is loaded, no further change is possible. In contrast to that, ByteSurgeon provides full runtime transformation capabilities: methods can be transformed even in a running system.

VOL 6, NO. 9 JOURNAL OF OBJECT TECHNOLOGY

http://argouml.tigris.org/ VOL 6, NO. 9 JOURNAL OF OBJECT TECHNOLOGY

Acknowledgments

We gratefully acknowledge the financial support of the Swiss National Science Foundation for the project "Analyzing, capturing and taming software change" (SNF Project No. 200020-113342, Oct. 2006 -Sept. 2008). We would also like to express our thanks to Nik Haldimann for TypePlug and Oscar Nierstrasz and Frédéric Pluquet and Roel Wuyts for their help in reviewing various drafts of this paper.

ABOUT THE AUTHORS

294

JOURNAL OF OBJECT TECHNOLOGY VOL 6, NO. 9