14.3. PROGRAMMING TRANSFORMATIONIn the transformation architecture employed by JOIE, there are four main roles: the target program, the transformer, auxiliary code, and run-time support classes. The target is the original, untransformed program. The transformer is a classor collection of classesthat contains the logic to edit and manipulate the target. As part of that manipulation, transformers may insert new, already compiled, units of functionality directly into the target; these units are found in auxiliary classes, which may be bundled together with the transformer. Auxiliary classes do not appear by name in the final running program; the transformer copies portions of their definitions into the target classes. Finally, a set of classes provides run-time support for the transformed classes. Typically, a transformer author bundles together the transformer, auxiliary classes, and run-time classes. The distinction between auxiliary classes and transformer classes is important. Auxiliary classes are not classes that assist the transformer. They do not execute during the transformation process. Rather, they contain fragments of code that the transformer inserts into the target. Auxiliary classes lose their identity during transformation. The transformed application contains their code but no references to their names. This is analogous to the "base/meta" distinction in metaprogramming [12]. Here, both the target and the auxiliary classes are base code, while the transformer is the metaprogram that operates on both. Additionally, the transformer can obtain base code from other sources, such as by internally containing or referencing program fragments or by dynamic generation during transformation. However, the use of auxiliary classes represents good transformer-programming style. Run-time classes are akin to auxiliary classes in that they do not execute during the transformation and are part of the resulting application. In contrast, however, the run-time classes are not copied into the target but retain their identity. Typically, the code found in auxiliary classes refers to run-time classes by name. Making the transformer an executable (rather than declarative) program has implications for both selecting sites for transformation and inserting code. Since the transformer is a fully fledged program with private state, it can be extremely selective and customized about where and how it chooses to operate. For example, it could choose transformation sites based on the other content of the target method, apply only a set number of transformations, or act only when a method met some criteria (such as being a leaf method). 14.3.1. SelectionKey to the design of JOIE is its ability to carefully specify the selection of targets of transformation, including the points at which to insert code and the selection of target classes. Contrast this to systems that provide a set of permissible join points, including call sites, field accesses, and method boundaries; these systems are more intuitive and simpler. JOIE, by allowing arbitrary access to instructions, provides a more powerful and more dangerous interface, especially when coupled with the programmable nature of transformers described previously. For example, a transformer in JOIE can limit a transformation to once per basic block or, more powerfully, can insert code only if that location is not dominated by code inserted earlier. Bytecode transformation can also target the entire program. Whole-program transformation can add a feature to every class of an application or, conversely, verify that certain properties are true of every loaded class. For example, one could extend every object in a system to be Observable. Whole-program transformation is best done at load time, as classes cannot execute in the JVM until they have been through the classloading process. In contrast, a selective transformation targets a specific subset of application classes. The targets for a selective transformer might be listed in a configuration file, or the transformer can infer the targets from inspection of classes that reference them. Sometimes, the programmer of the original, unmodified program will want to pass information about that program to a transformer to aid in selection. For example, while it can be difficult for a transformer to determine which methods leave a data structure in a consistent state, the programmer usually knows. In these cases, the programmer can define a labeling interface whose name is known to the transformer. A labeling interface is simply a standard Java interface (that is, a named set of method names with no implementations) that is used to pass information to the transformer. For example, if a method of a class were part of an interface called Consistent, the transformer would know to treat that method as one that left the data structure intact. 14.3.2. Use in AOSDSeparating the transformation logic from the inserted content is another important design choice. In binary architectures, aspects define their own placement. In composer architectures such as JOIE, a third entity applies aspects to designated locations in the code. These designs are not mutually exclusive, and most systems allow some mixture of the two, for example, by letting aspects refer to others. In general, however, composer architectures, especially when coupled with late or dynamic application of aspects (through transformation or otherwise), are an important building block for a more distributed and dynamic model of software development. Binary architectures are best suited toward the use of aspect-oriented programming as an augmentation to traditional programming, improving the ability of a single programmer or coordinated team of programmers to separate concerns. However, composer architectures additionally support a model in which applications can be assembled, transformed, and adapted by third parties, including the end user. This has particular application toward adding aspects to legacy code [10]; in an important sense, however, all code is legacy code. This broader model offers two advantages over tightly-bound aspect development: programs can be smaller, incurring the size cost of additional features only when clients request them, and transportable code can adapt to new services or interfaces made available by some hosts.
This has advantages for software management as well. Imagine an application that has five optional features implemented as bytecode transformations. That means there are thirty-two potential executable images of that program, one for every combination of included features. In a system that applies transformations only when requested, only the original (untransformed) program is stored. While postponing the decision of applying features does incur a (small) performance cost, it may be a preferable option when the potential executable images number in the hundreds or even thousands and when different users may require different subsets of available features.
However, most broadly, the advantage of this model of code development is that the potential number of programs increases significantly. If aspects, in whatever implementation form, remain locked inside development groups, they will only be used for a single application. If aspects are designed to be as orthogonal as possible, applicable to a broad range of target programs, then the number of potential programs increases as the product of available programs and aspects. |