Summary
The Eclipse Platform offers a comprehensive API (Application Programmer Interface) to developers writing plug-ins. This article discusses the general ground rules for using the Eclipse Platform API, including how to tell API from non-API, and how to stay in the API "sweet spot" to avoid the risk of being broken as the platform and its APIs evolve. These general ground rules are also recommended practice for plug-ins that must declare API elements of their own.
By Jim des Rivières, OTI
Updated May 18, 2001
What it means to be API
The Eclipse platform defines API elements for use by its clients, namely ISVs writing plug-ins. These plug-ins may in turn define API elements for their clients, and so on. API elements are the public face: they carry a specification about what they are supposed to do, and about how they are intended to be used. API elements are supported: the Eclipse platform team will fix implementation bugs where there is a deviation from the specified behavior. Since there is often a high cost associated with breaking API changes, the Eclipse platform team will also try to evolve API elements gracefully through successive major releases.
How to tell API from non-API
By their very nature, API elements are documented and have a specification, in contrast to non-API elements which are internal implementation details usually without published documentation or specifications. So if you cannot find the documentation for something, that's usually a good indicator that it's not API.
To try to draw the line more starkly, the code base for the platform is separated into API and non-API packages, with all API elements being declared in designated API packages.
- API package - a Java™ package that contains at least one API class or API interface. The names of API packages are advertised in the documentation for that component; where feasible, all other packages containing only implementation details have "internal" in the package name. The names of API packages may legitimately appear in client code. For the Eclipse platform proper, these are:
- org.eclipse.foo.* - for example, org.eclipse.swt.widgets, org.eclipse.ui, or org.eclipse.core.runtime
- org.eclipse.foo.internal.* - not API; internal implementation packages
- org.eclipse.foo.examples.* - not API; these are examples
- org.eclipse.foo.tests.* - not API; these are test suites
- API class or interface - a public class or interface in an API package, or a public or protected class or interface member declared in, or inherited by, some other API class or interface. The names of API classes and interfaces may legitimately appear in client code.
- API method or constructor - a public or protected method or constructor either declared in, or inherited by, an API class or interface. The names of API methods may legitimately appear in client code.
- API field - a public or protected field either declared in, or inherited by, an API class or interface. The names of API fields may legitimately appear in client code.
Everything else is considered internal implementation detail and off limits to all clients. Legitimate client code must never reference the names of non-API elements (not even using Java reflection). In some cases, the Java language's name accessibility rules are used to disallow illegal references. However, there are many cases where this is simply not possible. Observing this one simple rule avoids the problem completely:
- Stick to officially documented APIs. Only reference packages that are documented in the published API Javadoc for the component. Never reference a package belonging to another component that has "internal" in its name---these are never API. Never reference a package for which there is no published API Javadoc---these are not API either.
General Rules
The specification of API elements is generated from Javadoc comments in the element's Java source code. For some types of elements, the specification is in the form of a contract. For example, in the case of methods, the contract is between two parties, the caller of the method and the implementor of the method. The fundamental ground rule is:
- Honor all contracts. The contracts are described in the published Javadoc for the API elements you are using.
The term "must", when used in an API contract, means that it is incumbent on the party to ensure that the condition would always be met; any failure to do so would be considered a programming error with unspecified (and perhaps unpredictable) consequences.
- You must honor "must". Pay especially close heed to conditions where "must" is used.
Other common sense rules:
- Do not rely on incidental behavior. Incidental behavior is behavior observed by experiment or in practice, but which is not guaranteed by any API specification.
- Do not treat null as an object. Null is more the lack of an object. Assume everything is non-null unless the API specification says otherwise.
- Do not try to cheat with Java reflection. Using Java reflection to circumvent Java compiler checking buys you nothing more. There are no additional API contracts for uses of reflection; reflection simply increases the likelihood of relying on unspecified behavior and internal implementation detail.
- Use your own packages. Do not declare code in a package belonging to another component. Always declare your own code in your own packages.
Calling public API methods
For most clients, the bulk of the Eclipse API takes the form of public methods on API interfaces or classes, provided for the client to call when appropriate.
- Ensure preconditions. Do ensure that an API method's preconditions are met before calling the method. Conversely, the caller may safely assume that the method's postconditions will have been achieved immediately upon return from the call.
- Null parameters. Do not pass null as a parameter to an API method unless the parameter is explicitly documented as allowing null. This is perhaps the most frequently made programming error.
- Restricted callers. Do not call an API method that is documented as available only to certain callers unless you're one of them. In some situations, methods need to be part of the public API for the benefit of a certain class of callers (often internal); calling one of these methods at the wrong time has unspecified (and perhaps unpredictable) consequences.
- Debugging methods. Do not call an API method labelled "for debugging purposes only". For example, most toString() methods are in this category.
- Parameter capture. Do not pass an array, collection, or other mutable object as a parameter to an API method and then modify the object passed in. This is just asking for trouble.
Instantiating platform API classes
Not all concrete API classes are intended to be instantiated by just anyone. API classes have an instantiation contract indicating the terms under which instances may be created. The contract may also cover things like residual initialization responsibilities (for example, configuring a certain property before the instance is fully active) and associated lifecycle responsibilities (for example, calling
dispose() to free up OS resources hung on to by the instance). Classes that are designed to be instantiated by clients are explicitly flagged in the Javadoc class comment (with words like "Clients may instantiate.").
- Restricted instantiators. Do not instantiate an API class that is documented as available only to certain parties unless you're one of them. In some situations, classes need to be part of the public API for the benefit of a certain party (often internal); instantiating one of these classes incorrectly has unspecified (and perhaps unpredictable) consequences.
Subclassing platform API classes
Only a subset of the API classes were designed to be subclassed. API classes have a subclass contract indicating the terms under which subclasses may be declared. This contract also covers initialization responsibilities and lifecycle responsibilities. Classes that are designed to be subclassed by clients are explicitly flagged in the Javadoc class comment (with words like "Clients may subclass.").
- Restricted subclassers. Do not subclass an API class that is not intended to be subclassed. Treat these classes as if they had been declared final. (These are sometimes referred to as "soft final" classes).
Calling protected API methods
Calling inherited protected and public methods from within a subclass is generally allowed; however, this often requires more care to correctly call than to call public methods from outside the hierarchy.
Overriding API methods
Only a subset of the public and protected API methods were designed to be overridden. Each API method has a subclass contract indicating the terms under which a subclass may override it. By default, overriding is not permitted. It is important to check the subclass contract on the actual method implementation being overridden; the terms of subclass contracts are not automatically passed along when that method is overridden.
- Do not override a public or protected API method unless it is explicitly allowed. Unless otherwise indicated, treat all methods as if they had been declared final. (These are sometimes known as "soft final" methods). If the kind of overriding allowed is:
"
implement" - the abstract method declared on the subclass must be implemented by a concrete subclass
"
extend" - the method declared on the subclass must invoke the method on the superclass (exactly once)
"
re-implement" - the method declared on the subclass must not invoke the method on the superclass
"
override" - the method declared on the subclass is free to invoke the method on the superclass as it sees fit
- Ensure postconditions. Do ensure that any postconditions specified for the API method are met by the implementation upon return.
- Proactively check preconditions. Do not presume that preconditions specified for the API method have necessarily been met upon entry. Although the method implementation would be within its rights to not check specified preconditions, it is usually a good idea to check preconditions (when feasible and reasonably inexpensive) in order to blow the whistle on misbehaving callers.
- Null result. Do not return null as a result from an API method unless the result is explicitly documented (on the specifying interface or superclass) as allowing null.
- Return copies. Do not return an irreplaceable array, collection, or other mutable object as the result from an API method. Always return a copy to avoid trouble from callers that might modify the object.
Implementing platform API interfaces
Only a subset of the API interfaces were designed to be implemented by clients. API interfaces have a contract indicating the terms under which it may be implemented. Interfaces that are designed to be implemented by clients are explicitly flagged in the Javadoc class comment (with words like "Clients may implement."). A client may declare a subinterface of an API interface if and only if they are allowed to implement it.
- Restricted implementors. Do not implement an API interface that is documented as available only to certain parties unless you're one of them. In many situations, interfaces are used to hide internal implementation details from view.
Implementing public API methods
See "Overriding API methods".
Accessing Fields in API classes and interfaces
Clients may read API fields, most of which are final. Certain struct-like objects may have non-final public fields, which clients may read and write unless otherwise indicated.
- Null fields. Do not set an API field to null unless this is explicitly allowed.
Casting Objects of a known API type
An object of a known API type may only be cast to a different API type (or conditionally cast using instanceof) if this is explicitly allowed in the API.
- Cast and instanceof. Do not use instanceof and cast expressions to increase what is known about an object beyond what the API supports. Improper use exposes incidental implementation details not guaranteed by the API.
And, of course, casting any object to a non-API class or interface is always inappropriate.
Not Following the Rules
Whether done knowingly or unwittingly, there are consequences for transgressing the rules. It might be easier for all involved if there were API police that would bust you for breaking the rules. However, that is not the case. For the most part, API conformance operates as an honor system, with each client responsible for knowing the rules and adhering to them.
The contracts on the API elements delimit the behavior that is supported and sustained. As the Eclipse platform matures and evolves, it will be the API contracts that guide how this evolution happens. Outside of these contracts, everything is unsupported and subject to change, without notice, and at any time (even mid-release or between different OS platforms). Client code that oversteps the above rules might fail on different versions and patch levels of the platform; or when run on different underlying OSes; or when run with a different mix of co-resident plug-ins; or when run with a different workbench perspective; and so on. Indeed, no one is even particularly interested in speculating exactly how any particular transgression might come back to bite you. To those who choose to ignore the rules, don't say that you weren't warned. And don't expect much more than a sympathetic "Told you so."
On the other hand, client plug-in code that lives by the above rules should continue to work across different versions and patch levels of the platform, across different underlying OSes, and should peacefully co-exist with other plug-ins. If everyone plays by the rules, the Eclipse platform will provide a stable and supported base on which to build exciting new products.