This is more or less a mechanical transformation.
Methods that return collection types such as List, Vector, Map, Hashtable,
ArrayList, should where possible return parameterised types such as List<String>.
Arguments to methods and constructors that are of collection types should where possible parameterise them. This may cause warnings for client code. For example, if we change the constructor ObjectName(String,Hashtable) into ObjectName(String,Hashtable<String,String>), then existing code that calls this constructor with Hashtable alone will produce a warning (probably not even that if -source 1.5 is not specified). But it will compile and work. From the JSR 14 spec:
"The reverse assignment from Vector to Vector<String> is unsafe from the standpoint of the generic semantics (since the vector might have had a different element type), but is still permitted in order to enable interfacing with legacy code. In this case, a compiler will issue a warning message that the assignment is deprecated."
As a minimum test of correctness, the genericized code must compile without warning and the existing JMX 1.2 TCK must pass (except possibly the signature test).
Unfortunately, making this change in Tiger conflicts with the inclusion of JMX 1.2 in J2EE 1.4. With this change in place, it would be possible to write JMX code that compiles with J2EE 1.4 + J2SE 1.5 but not with J2EE 1.4 + J2SE 1.4, even though it uses an interface (JMX 1.2) that is supposed to be present in J2EE 1.4. So we cannot make this change before the next revision of J2SE.
(Example: ObjectName names = MBeanServer.queryNames().toArray(new ObjectName). With J2SE 1.4, requires a cast (ObjectName).)
Unfortunately making this change to J2SE 1.5 could cause any implementation of J2EE 1.4 to fail the signature test from the J2EE Compatibility Test Suite. So we can't make this change now. It will have to wait for the next version of J2SE.
Reopening this bug and committing for Mustang (J2SE 6.0).
I have now made a generification pass over the JMX API. The changes in the Description section could all be applied, except that AttributeList, RoleList, and RoleUnresolvedList cannot be made to extend ArrayList<Attribute> (etc) rather than plain ArrayList. The reason is that they all define e.g.
void add(Attribute attr);
whereas ArrayList<Attribute> defines
boolean add(Attribute o);
The conflicting return types mean that AttributeList no longer compiles if it is made to extend ArrayList<Attribute>. Instead, we can define a constructor:
to convert from a List<Attribute> to an AttributeList, and a method:
to convert back (in fact, an unsafe cast suffices for the latter). The same remarks apply for RoleList and RoleUnresolvedList except that they already have a (List) constructor which just needs to be generified.
There are some additional opportunities for generification:
- MBeanServerInvocationHandler.newProxyInstance can be defined as:
public static <T> T newProxyInstance(..., Class<T> interfaceClass, ...)
which means that where you previously wrote:
MyMBean proxy = (MyMBean) MBSIH.newProxyInstance(..., MyMBean.class, ...)
you can now omit the cast.
- The two-argument StandardMBean constructor can be generified as:
public <T> StandardMBean(T impl, Class<T> mbeanInterface)
which means that the compiler will check that impl does
indeed implement the given interface.
- OpenType can be generified as OpenType<T> so that the Java type of
the corresponding instances can be specified. For example,
SimpleType.INTEGER is an OpenType<Integer>; every CompositeType
is an OpenType<CompositeData>. This allows the compiler to check
that the default, legal, and bound values in an OpenMBeanAttributeInfo
or OpenMBeanParameterInfo are valid for the given OpenType. Thus we
have e.g. this generified constructor:
public <T> OpenMBeanParameterInfoSupport(String name,
- MBeanServerPermission.elements returns Enumeration<Permission>.
Concerning the SDN comment about the use of ArrayList in the API:
I think everyone can agree that the API should not have used ArrayList anywhere. But that dates from the earliest days of the API, when best practices for API design were perhaps less well-known.
I do not think there is anything to gain from adding parallel methods that return List. For example, we could certainly add MBeanServerFactory.findMBeanServer2 that returns List<MBeanServer>. However, that would be awkward, since we would have to invent a different name for every such new method; two methods can't differ only in their return types. And it would make no real difference to users of the API, who can and should already write:
List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(...);
Fortunately, the API doesn't contain any places where *parameters* use concrete Collection types rather than interfaces. That would be much more inconvenient for users.
A further restriction is that we cannot add new methods to interfaces if there is any chance that user code might have implemented those interfaces. Since we don't explicitly discourage that anywhere, that means that all interfaces in the API are frozen.
The generification rationale at http://weblogs.java.net/blog/emcmanus/archive/generification.txt may be of interest for further information about the details of how this API was generified.