Declaration model.
Similarities to reflection
Method names
This package's method names should match method names in reflection as
closely as possible. This allows callers who are already familiar with
reflection API to learn this API quicker. Examples:
ClassD#getEnclosingMethod()
,
ClassD#getInterfaces()
,
GenericD#getTypeParameters()
.
Differences from reflection
No special getGeneric*()
methods
Consider, for example, java.lang.reflect.Method#getReturnType()
Method.getReturnType()
. Though the return type of a method is a
generic type, getReturnType()
must return the type
erasure of the return type for compatibility with callers compiled
against the jdk < 1.5 reflection API. Thus, in order to get the real
return type of a method in the jdk >= 1.5 reflection API, callers must
call java.lang.reflect.Method#getGenericReturnType()
Method.getGenericReturnType()
.
This API is not bound by said requirement and is free to return the
real return type of a method, generic or not. This trims down the size
of the API and improves the readability of caller code that operates
upon the real return type instead of the return type's erasure. We
argue that the set of callers operating on the real return type form
the majority. There is no loss of functionality because callers who
want the type erasure can call
getReturnType().getTypeErasure()
.
Non-empty TypeD
The jdk >= 1.5 reflection API requires that a caller casts a Type to
be a Class (or other subtype) before calling methods such as
getMethods()
and isAssignableFrom(C)
. While
technically correct, it is a very clunky API for IDE features to
use. Because many of said operations are performed regardless of
whether the target type is a class, type variable, wildcard type,
array type, etc., these have been pushed into the supertype
TypeD
.
Here are some notable additions above what java.lang.Class
provides:
-
getHierarchy()
. Returns a type's list of base
classes and a base interfaces without duplicates. Used variously by
refactoring and browsing IDE features.
-
getTypeErasure()
. Ensures that there is no loss of
functionality with the removal of getGeneric*()
methods. See above discussion.
The hidden clinit
method
Though the reflection API does not provide access to the hidden
clinit
method, certain IDE features need access to it.
In particular, debugging features require access to the method's line
number table for determining if a breakpoint may be set on a
particular line of code.
getName()
returns simple names
All implementations of HasNameD#getName()
should return
simple names. The only place this deviates from the reflection API is
that ClassD#getName()
returns its simple name instead of
java.lang.Class#getName()
which returns a fully
qualified class name or a FieldDescriptor name.
The benefit is that the name returned by ClassD#getName()
conforms strictly to the JLS whereas the name returned by
java.lang.Class#getName()
conforms neither to the JLS nor the JVMS.
To get the fully qualified source name of a class, use
TypeD#getSourceName()
, conformant to the JLS3. To get the fully
qualified type descriptor of a class, use
TypeD#getTypeDescriptor()
, conformant to the JVMS3.
getCanonicalName()
?
We use the method TypeD#getSourceName()
instead of
java.lang.Class#getCanonicalName()
. The two problems posed by
getCanonicalName()
is that it is only valid on raw types
and that JLS3 isn't out yet which means no one actually knows what
"canonical name" means. The use of the term "source name" is both more
readable and more general. It applies equally well to parameterized
types, type variables, and wildcard types.
Never return a null
String
A String typed method should NEVER return null if the specified value
is not present. A String typed method may only return null if the
method call itself is irrelevant. An example of where the reflection
API returns a null String is java.lang.Package#getName()
for the root package. Returning the empty string for the root package
allows callers to fill a Map with PackageD
values and
PackageD#getSourceName()
keys.
However, it is unclear what to return for String typed methods where
the result is irrelevant. Consider TypeD#getSourceName()
for anonymous inner classes. An anonymous class may not be referred to
in a source compilation unit, rendering the call to
getSourceName()
irrelevant. Returning null
poses a problem preventing callers from filling a Map with
TypeD
values and the TypeD#getSourceName()
keys. Special case handling decreases code readibility. However,
returning the empty string also poses a problem because then two
anonymous classes will share the same key.
We choose to return the empty string in this case with this reasoning.
Consider that TypeD#getSourceName()
uniquely identifies a type
in a specific scope. That is, suppose there is a type variable
T
and a local class T
both accessible at a
given point in a source compilation unit. (Yes, that will generate a
compile error but unlike the reflection API, this API must be able to
handle erroneous compilation units.) However, one type will always
hide the other, therefore it is safe to keep a Map of visible TypeD's
at a given point in a source compilation unit. A refactoring feature,
say, may want to be able to insert the value of
getSourceName()
into that given point in the source. If
getSourceName()
returns the empty string, then the caller
will simple insert nothing into the buffer.