next up previous
Next: Acknowledgment Up: Pluggable Reflection: Decoupling Meta-Interface Previous: Typed Reflection


Conclusion

People tend to think of reflection as a language feature (and thus coupled to the language) and reflection code as coupled to the language implementation. In this paper, we look at reflection more abstractly as just another interface with the potential for multiple implementations. We consider how to retarget a reflective program to another meta-information source such as a repository, and the benefits of doing so. Indeed, retargetability could even apply across languages: Why couldn't reflectdoc also run on Smalltalk?

To demonstrate reflection as an interface, we took an abstract grammar of a Java class definition and applied the INTERPRETER pattern to it, producing a mirrored class hierarchy with the same interface as reflection. This hierarchy can serve as a class repository by generating a parser that converts source code to instances of the mirrored classes. In particular, visitors written for the INTERPRETER pattern are no different than other clients--they can be retargeted to use reflection as well.

We demonstrated three pluggability approaches:

  1. Legacy clients of the reflection interface can be retargeted to use the mirrored hierarchy without modification apart from changing import statements.

  2. New clients that use visitors as building blocks are pluggable at compile-time.

  3. For even looser coupling, third-party clients may use components as their building blocks, affording full assembly-time pluggability.

The key contribution of this paper lies in staging the problem. In an evolutionary path from conventional to pluggable reflection, mirroring reflection is merely the first step: a BRIDGE [13] for decoupling users from providers of meta-information. Clients cannot tell the difference between mirrored and genuine reflection. Implicit invocation adds flexibility through looser coupling, allowing third-party composition of reflection information and clients. The Holy Grail, of course, is to give clients uniform access to metadata sources with disparate and unforeseen semantics.

An obvious limitation of mirroring is that the reflection and repository APIs must be kept in sync. Unfortunately, interfaces invariably evolve. A single interface may go through several versions; parallel interfaces may diverge. How do you reconcile two interfaces that are similar but not identical? Programming to the least common denominator may be unsatisfactory because you cannot exploit the unique advantages of either interface. On the other hand, specialized interfaces make switching implementations difficult, thereby forgoing the benefits of encapsulation, portability, and testability.

Both kinds of interface evolution are found in Java's reflection API. Reflection did not exist in JDK 1.0. Had you wanted to implement a class browser, you would have needed a repository. JDK 1.1 introduced reflection, and you could have migrated your class browser to use it instead. The initial reflection API did not support all class information, however, and so the repository still would have been useful. JDK 1.2 added the ability to access the package that a class belongs to, and it introduced the Doclet API, which can be used instead of a repository. JDK 1.3 added the ability to define proxy classes dynamically, thereby enabling limited behavioral reflection. JDK 1.3 also added the Mirror API, yet another reflective facility as part of the Java Debugging Interface (JDI).

The proliferation of APIs poses a dilemma: How does one design client code that can work with current and future APIs with minimal (ideally zero) change? Printer driver interfaces are a compromise between generic and specialized printing features. Unique or advanced features are typically eschewed to maximize compatibility. As a result, an application that needs a specialized feature must circumvent the printer driver or forgo the feature.

Meanwhile, the role of reflection is expanding. Components in particular rely heavily on reflection, and clients need to understand how so. With increased awareness of reflection comes the need for supporting tools. Traditional tools for source code, such as code beautifiers, metrics and documentation generators, and the like would be most welcome if they worked on reflection. Unfortunately, they rarely do.

Now imagine a world in which reflection is pluggable. Tools can run on source code files, on class repositories, or on reflection without change and without the information sources' a priori knowledge of each other. Different trade-offs among the sources permit the tool to optimize itself to its context.

Testing would come of age in this world. One scenario: A third-party framework comes with a semantic-checking tool for certifying correct customization of the framework. The checker can run on the source code to catch syntax errors. It can also work on class hierarchies to catch semantic errors (such as forgetting to subclass a framework class). And it can run on reflection to check for certain semantic violations that (thanks to the halting problem) can only be discovered at run-time. Moreover, the checker can cache and compare test results, as we did with the javadoc and reflectdoc outputs, to spot inconsistencies. These are some of the benefits we can expect of fully pluggable reflection.


next up previous
Next: Acknowledgment Up: Pluggable Reflection: Decoupling Meta-Interface Previous: Typed Reflection
David H. Lorenz 2003-02-17