My last post on JSR 277's version number fiasco (which was cross-posted on DZone) discussed the futility of a version scheme used by almost no-one, but caused a number of comments to the effect that 4 version numbers being used by Oracle, Firefox, and of course Sun's JDK.
This makes one very important error by association; all of those version numbers are products. That is Oracle is a product, Firefox is a product, the JDK is a product. None of them are components in the strict sense of the word; and whilst I'm sure someone is going to leave a comment saying "but you can embed Firefox", that's not the point either. Embedding a product into your app isn't a component dependency using the same module system; there's no OSGi "Firefox" bundle, and there's no JSR277 "Firefox" module. So whilst XULRunner can be used, that's a library which may not need to have the same versioning semantics as a product that contains it.
An example of product-versus-library is WebKit, the browser library that was first to release (although not first to claim) a build that fully passed the Acid3 test. The version that passed is generally referred to as r31326, since that's the SVN trunk version that was used to compile and build the code. But (an older version of) this is embedded as part of Safari 3.1, and also other mobile device platorms (including, of course, the iPhone).
The point is that the versioning system of the underlying components can be completely different from the version number of a product-as-a-whole, which is (invariably) a collection of components underneath. One may have a user-friendly name (Leopard), a numerical version (10.5.3), a build tag (9D34) or indeed other ways of representing the install (
/System/Library/Frameworks/System.framework/Versions/B). So arguing for a particular versioning scheme based on the versioning that may be used for marketing (or other) purposes is somewhat futile; both can co-exist and mean different things to different people. Eclipse learned the lesson in the 3.1 days, and since then, Eclipse 3.4 contains plugins like
org.osgi.util_3.1.300 which are entirely distinct from their product name, since they haven't changed other than bugfixes (i.e. no API or incompatible changes). Eclipse's notes on Version numbering are recommended reading for anyone maintaining large systems. Indeed, Eclipse has moved away from version numbers for the product to use code-names like Europa and Ganymede.
Now let's get back to the subject in hand. The JSR277 versioning is different from every other component model in deference to of a choice of product naming. Frankly, we know that the marketing names of each successive release of Java are going to change, so why pick a version system that's known to be a failure already? In fact, in Stanley Ho's rebuttal of my previous post, he states:
The JDK uses four numbers like 1.2.2_18 or 1.4.2_16 because of its longstanding practice that the major version is 1. You can argue whether this is good practice, but it's the way the JDK is.
Indeed, the choice of product names of the JDK hasn't ever been good. But it's not like the JDK is being componentised into modules (like Harmony has) — it's going to be One Big Blob. And let's face it, if you're writing Java code, you have to depend on the JDK anyway, so it's not like it will be an optional dependency anywhere.
So, why not use three numbers to represent the version, and define that for JSR277 there's a "1." appended to the front? That seems like a reasonable choice, and follows Java's lead. We could have a long-standing practice of depending on log4j 188.8.131.52, use commons-net 184.108.40.206, compile code with Antlr 220.127.116.11, and even use JSP 18.104.22.168 to write Web 1.2.0 mashups. Alternatively, note that _18 and _16 are exactly the same length (and in the past have been prefixed by zeros, such as _01) so that they are equivalently comparable lexically as well as numerically.
It's not just OSGi that JSR277 is ignoring. Maven is the default tool of choice for building large componentised applications in the open-source world; and whilst Ant is used widely, as the number of components grows the proportion of build systems swings strongly in Maven's favour.
Maven's repository defines a standard mechanism for representing versions, using OSGi's concept of three numeric digits, along with an additional fourth numeric build digit and textual qualifier. The key aspect here is that the first three numbers are the important ones for determining compatibility - the last two parts are generally for tagging the version of the build. You can search the repository for Apache Jars (or anything with “ja”) to see who uses what version scheme; many of them in fact only use two digits (i.e. those before the “-” separator), with almost everything else using three digits. (In the OSGi world, Eclipse uses a combination of date stamp for the qualifier – since that's a fixed-width format – and so lexicographic ordering is the same as numerical ordering.)
In fact, there's only one set of Jars (that I found in Maven's repository) that has four numeric digits; Apache Derby, a project initially donated by IBM. Not only that, but whilst 10.4.1.3 exists, 10.4.1.2, 10.4.1.1 and 10.4.1.0 never existed as stable released builds, and there will never be a 10.4.1.5 or above until 10.4.2.x hapens. Observe that this is possible in OSGi too; the last digit merely becomes the qualifier and is compared lexically. If you are building release candidates, like in this case, you could release 10.4.1.RC3 instead. I'll let you debate about what happens if you have more than 9 Release candidates (one option might be to fire your QA and Dev teams; or you could plan ahead and do RC01; or you could go from RC9 to RC91, RC92; or even RC3.14159... and add a new digit on each time — it worked for TeX, after all, whose version number asymptotically approaches π over time by definition).
So, despite the majority of the known Java universe using three or less numerical digits for their versions, why does Sun (and let's not forget that it's Sun pushing this through, not the expert group who have never been consulted on the number of version numbers) feel the need to go to four digits based on previous incompetence? In any case, all the existing JDK versions (e.g.
1.5.0-13-119) are expressible as OSGi versions (e.g.
22.214.171.124-119); if you look back at all the archive releases for the JDK downloads, the number after the underscore has been a zero filled two digits (or three in the early days of Java 1.2). So we can compare textually in exactly the same way for the special cases of both Sun and Derby, and live with exactly three numbers with no loss of either generality or impacting any existing or future Java releases. In short, four numeric versions are both unnecessary and confusing, and incompatible for the sake of incompatibility. The last company that made incompatibility a priority is not doing so well in the market place now and has a bleak long-term future; and it's not just JSR277 that Sun's shooting in the foot but potentially the future of Java as well, being squeezed as it is between C# on one side, dynamic languages on the other, and multimedia runtimes like Silverlight and Flex from everywhere else.
First, the Java Module System (in the forthcoming JSR 277 EDR2) adopts the OSGi syntax for version range intervals. [ and ] mean the bound is inclusive and ( and ) mean the bound is exclusive:
- [1.2.3, 4.5.6) ~ 1.2.3 <= x < 4.5.6
- (1.2.3, 4.5.6) ~ 1.2.3 < x < 4.5.6
- [1.2.3, 4.5.6] ~ 1.2.3 <= x <= 4.5.6
- (1.2.3, 4.5.6] ~ 1.2.3 < x <= 4.5.6
All these intervals have the same meaning in the Java Module System as they do in OSGi.
It then goes to back-track on the good progress made to assert the following:
There is no doubt that someone seeing 1.2.3 for the first time thinks "exactly 1.2.3", so that is how the Java Module System interprets it. Ranges are denoted with explicit symbols. '+' means "or greater":
- 1+ ~ [1, infinity) ~ 1 <= x < infinity
- 1.2+ ~ [1.2, infinity) ~ 1.2 <= x < infinity
- 1.2.3+ ~ [1.2.3, infinity) ~ 1.2.3 <= x < infinity
- 126.96.36.199+ ~ [188.8.131.52, infinity) ~ 184.108.40.206 <= x < infinity
Actually, I doubt that very much. I also doubt that Stanley ever posted the question to the expert group, either for the EDR2 case or indeed before in the original specification of what 1.2.3 means. (Feel free to correct me by pointing me to the expert group observer list page with the mail if I'm wrong, but I searched and could not find one.) Perhaps he's suggesting that if you write an application and depend on version (say) 220.127.116.11, then you are immediately doomed if you upgrade to a newer version because everything will suddenly stop working? I also doubt whether anyone has been asked the question “So, if I put Log4J 1.2.15 as a dependency, that will allow me to use a bugfixed Log4J 18.104.22.168, right?” Nope, it seems not. If you use the default terse form, every time a new patch version is released you have to rebuild all your dependencies to take that into account.
The astute of you will have noticed that there is almost never, in the practical sense, any point in depending on precisely one version of a component. So there's always going to be a case of "at least" rather than "exactly" in the dependencies. Note that if you only ever have one version of a component, "at least" and "exactly" are equivalent; but at runtime, they can change (for bug fixes etc.). Fragile components that depend on precisely one version of another component will never be able to be bug-fixed by replacement of a newer version of that component. I can't stress what a huge mistake this is. Depending on exactly a single version is always, always the wrong thing to do. You'll never find a JSR277 module or even OSGi bundle ever doing this, other than by mistake. (Note that whilst Maven specifies a specific POM dependency to compile against, the application can run against any different version; that's actually good because you can compile against 1.5.0 and test against 1.6.0, to ensure that you don't depend on any new APIs that might have been introduced in 1.6.0.)
You'll also have noticed that an OSGi version of
1.2.3 corresponds exactly to a JSR277 version of
1.2.3+. Yes, that's right - for the common case the JSR277 version is always one character longer. Then there's the fact that there's two choices of
*, and that if you get the wrong one, you don't get the right forward compatibility. OSGi is more explicit; you either want “at least” 1.2.3, or you want a fixed range between two values so you know what the upper limits are. For example; consider the following list:
How many of those do you instantly know what the upper and lower ranges are? Now, what about:
- [1.2, ∞)
- [1.5, 1.6)
- [1.2.1, 1.2.1]
- [1.2.3, 1.3)
- [1.2.3, 2.0)
- [1.2.3, 1.3)
Anyone who thinks the former are more instantly understandable by humans to the latter is either someone writing the JSR277 spec or someone who has been implementing the JSR277 spec. In all other cases, the OSGi range is clearer, with the only potential issue being caused by the end range being exclusive or inclusive. (No OSGi bundles actually use an exclusive start range or an inclusive end range; the only time you might erroneously consider using that is when using (say) Mac OS X 10.5.0, because no-one uses .0 versions ... but since a range of (10.5.0, ∞) would admit 10.5.0.1 as a valid value, what's usually desired is instead [10.5.1,∞) — or more likely [10.5.1,10.6).).
So, the + vs * syntax is immediately confusing and less understandable, as shown above. Not only that, but the common case (of a developer requiring a minimum level of support) is shorter and less error prone in OSGi than in JSR277. And whilst it's possible to define a precisely one version policy in both OSGi and JSR277, the former makes you go out of your way to declare that whilst the latter gives it as the default option. Yes, JSR277 makes it really easy for you to screw up your dependencies. You know you can't use 1.2.3; but you also don't know what the difference between 1.2.3+ 1.2.3* or 1.[2.3+] or 1.[2.3*] would give you.
It seems that I'm not the only one unimpressed by the current state of affairs. Peter Kriens wrote:
Can you spot the difference between minor and micro? I can't, and that is the reason that OSGi proposes the following convention: incompatible (major), backward compatible (minor), no API change/bugfix (micro), and builds or variations (qualifier). That is, our micro is Sun's update because Sun's minor and micro appear to have an identical purpose.
And ever thought about the concept of largely backward compatible? Isn't that something like a little bit pregnant?
Wouldn't it be a lot more productive for all of us if Sun would just adopt the OSGi scheme? There is lots of work to do on the module API, why not reuse existing specifications where you can? And if the OSGi scheme has burning issues, why not report that as a bug or change request; after all Sun is a distinguished OSGi member.
Others disagreed with the weak rationale for inventing a custom incompatible versioning convention, and called it as they saw it:
The absolute last thing we need from Sun in the Java Modules is Yet Another Brilliant Sun Shiny Invention that we'll have to suffer though for years until all the bullshit is extracted from the hubris that went into creating.
Stop the madness. Someone please hold an intervention with these people. The industry simply cannot stand to have this crap thrown at them, especially in something as fundamental and as far reaching as the frickin' base Java module system.
Sometimes a turd is just a turd.
In conclusion, given that up to three numeric version numbers is universally accepted both by OSGi and non-OSGi bundles, and that the only special case is Sun's desire to use four numbers for the JDK (but whose fourth number is a fixed size for the release history of all past and present releases), why don't we just standardise on three numeric and one lexicographic version identifier? That would work for both Derby and the JDK's numbering system.
The versioning operators are less immediately accessible than a defined range, and have a default that encourages extreme fragility. The only reason why union ranges are needed is because the JSR277 versioning operators are not sufficiently powerful to be able to declare a range like [1.2.3, 4.5.6). Given that the OSGi range format is now usable, we don't even need it for that case. The only thing that is possible in JSR277 that is not possible in OSGi are non-continuous ranges (e.g. 1.1+;3.1+;5.1+), but that is a use case that has never been needed to date. If plenty such use cases can be found, I'm sure it could be considered for a future release of OSGi.
Lastly, the stated goal of JSR277 is to be compatible with OSGi. There's much less scope for bugs and decisions on how to map version numbers together if they both use the same format. There's no need to re-invent a version numbering system that has years of experience demonstrating that it works in favour of a theoretical never-been-tested solution which is known to have issues. The only possible reason is because three isn't enough; but as I've said here, using a fourth digit of known size in the qualifier will work for the only cases that are known to use that.
Ideally, this discussion should be raised on the JSR277 expert group list; though mere mortals such as you and I cannot post, only view the (lack of) traffic on the list. I'd therefore like to request that someone who is on the expert group bring up the issue of number of numbers before it is too late; because the point of an expert group is to be able to discuss, dissect and determine what should logically be needed, even if the implementation is done behind closed doors. If that doesn't work, you could always try a try mailing firstname.lastname@example.org, if anyone is listening on that address.
Modules in Java are important to get right, because once the versioning system is set in stone, it won't be changed. This isn't about OSGi; it's about the future of the Java platform. We've got to get the version representation right so that other implementations of JSR277 (JAM, Maven, ...) can work together; otherwise, it will shrivel and die, maybe even taking the Java platform along with it.