A reflective computation [30,31,23] is one that has an accurate representation of itself, and the computation is consistent with its representation. A change to the computation appears in the representation and vice versa. Many object-oriented languages (OOLs) provide a mechanism--reflection--that supports reflective computation explicitly. Reflection is a versatile tool, applicable at run-time, compile-time, load-time, and during component assembly.
A tenet of software development in general and object-oriented design in particular is to program to an interface. Doing so allows client and implementation to evolve separately, thereby facilitating code evolution, portability, and testability. Maintaining several native implementations for the same interface also promotes portability, much as multiple back-ends do for a compiler. Swappable implementations can even simplify prototyping and testing, say, by providing a choice of instrumented, optimized, and stubbed-out implementations.
Unfortunately, no mainstream OOL extends the benefits of programming to an interface to its reflection facilities. Code that uses reflection is assumed to access the reflective representation--they are one and the same. The result is a situation resembling the one that gave rise to printer drivers. Back then, each application was tailored to work with specific printers. Buying a printer with new or different features required upgrading or replacing the application. Something similar can be said of current applications that use meta-information.
The problem lies in a strong coupling between the reflection interface and its implementation. A debugger, for example, may obtain information about a program being debugged either from a source code repository or through reflection. Debuggers usually work with one information source or the other, not both; changing the source would require substantial rework of the debugger. But that needn't be the case. Reflection is just another application programming interface (API) that can have several implementations.
Consider how a printer driver defines a standard API through which printer and application communicate. Any application that can print may use the printer driver's interface, confident that it will work subsequently with new printers without change. Conversely, a printer needn't be aware of the applications it serves; it can rely on the driver to relay status and problem information to applications. In the same way, decoupling the reflection interface from its implementation affords the novelty of more than one implementation of meta-information. Then third parties, for example, could plug the debugger into either a source code repository or reflection. We call this vision pluggable reflection.
The paper demonstrates the potential advantages of pluggability by illustrating two extralingual approaches to pluggable reflection. In the first, clients of reflection are structured as visitors of objects representing a grammar (i.e., an instance of the INTERPRETER pattern [13]). For added flexibility, the second approach implements INTERPRETER dynamically rather than statically using a set of event-based ReflectionBeans components.
Both examples are based on an abstract syntax representation of reflection. Programming against a grammar as opposed to a physical implementation of reflection is key to retargeting clients of meta-information to different sources. In fact, the INTERPRETER pattern's class hierarchy can mimic Java's reflection facilities precisely enough that clients can use one or the other just by changing import statements.
The ability to retarget tools to different meta-information sources is not only useful but sometimes necessary. In Section we motivate this work by explaining the trade-offs between two meta-information sources. In Section we show the benefits of choosing the most appropriate source, and then in Section the benefits of using both. We describe our experience implementing a javadoc-like tool that can be easily retargeted to various reflective sources. An alternative implementation strategy applies the object-to-component transformation that we present elsewhere [22] to produce JavaBeans from Java's Reflection API.