This is a simple package for units and dimensions, based on Andrew Kennedy's Ph.D. work. It includes dynamic checks for unit compatibility. In addition, it can statically check for errors using an annotation processor with an experimental version of the JSR 308 tools. Specifically, the experimental version supports annotation arguments that are Java expressions.
The package was developed by Lex Spoon.
To use this system, check out
the JSR 308 langtools
and checker framework. Put them in sibling directories, and name
the directories checkers
and langtools
.
Then, apply this patch
with patch -sp1
. (TODO: convert the patch to a Mercurial
branch) Build langtools
and checkers
as
usual, and use the resulting javac
for the rest of this
tutorial.
The following import pulls in the units API:
import checkers.units.model.*;
Additionally, many standard units, such as meters and seconds,
are available in class StandardUnits
. These can be
imported as follows:
import static checkers.units.StandardUnits.*;
Given those imports, expressions like the following are legal:
Measure distance = m.times(5); // 5 meters Measure time = s.times(10); // 10 seconds Measure speed = distance.div(time); // 0.5 meters per second
Expressions that have mismatched units will raise a run-time exception:
Measure nonsense = distance.plus(time); // IncompatibleUnitsError
Here is a complete example program putting it all together:
import checkers.units.model.*; import static checkers.units.model.StandardUnits.*; public class UnitsExample { public static void main(String[] args) { Measure distance = m.times(5); Measure time = s.times(10); Measure speed = distance.div(time); System.out.println("Speed = " + speed); Measure nonsense = distance.plus(time); } }
Compile and run the example with checkers-quals.jar
on
your classpath:
../langtools/dist/bin/javac -cp ../checkers/checkers-quals.jar UnitsExample.java java -cp ../checkers/checkers-quals.jar:. UnitsExample
You should see a printout of the speed followed by
an IncompatibleUnitsError
:
Speed = 0.5 m/s Exception in thread "main" checkers.units.model.IncompatibleUnitsError: Incompatible units: s vs. m
5 kilometers.
kilometers.
Length.
The library will automatically propagate units through your
computation. It will attempt to maintain the units you enter when
possible, but will convert them if you add or subtract units that are
compatible but different. Any attempt to add or subtract measures
with incompatible units will result in
an IncompatibleUnitsError
.
Units errors can be found at compile time rather than at run time.
To do so, simply compile with checkers.units.UnitsChecker
as an annotation processor. For example:
../langtools/dist/bin/javac -cp ../checkers/checkers.jar \ -processor checkers.units.UnitsChecker \ UnitsExample.java
In this case, no errors are produced. That's because all of the
variables in this example are annotated as an
arbitrary Measure
. The checker ignores operations on an
unannotated Measure
, just like a
gradual
type checker ignores operations on an untyped variable.
To make the checker useful, you must add annotations to
the Measure
types to indicate the expected dimension of
the measure involved. The annotation type is Dim
. It
is available via the following import:
import checkers.units.quals.Dim;
The argument to Dim
is a dimension, such
as Length
or Time
. Here is an updated
example with Dim
annotations on all Measure
types.
import checkers.units.model.*; import static checkers.units.model.StandardUnits.*; import checkers.units.quals.Dim; public class UnitsExample { public static void main(String[] args) { @Dim(Length) Measure distance = m.times(5); @Dim(Time) Measure time = s.times(10); @Dim(Length.div(Time)) Measure speed = distance.div(time); System.out.println("Speed = " + speed); @Dim(Length) Measure nonsense = distance.plus(time); } }With the annotations in place, the following error is emitted:
UnitsExample.java:13: (Incompatible types: Length vs. Time) @Dim(Length) Measure nonsense = distance.plus(time); ^ 1 error
As you can see, if you want to get the most of the static units
checker, you must systematically use a Dim
annotation on
every Measure
type.
When using a Dim
annotation, you must supply an
argument that the processor understands, or it will complain. The
argument must be a static dimension expression. A static
dimension expression is any of:
Dimension
Dimension.SCALAR
, for scalar quantities
dim1.times(dim2)
, for
any static dimensions dim1 and dim2
dim1.div(dim2)
, for
any static dimensions dim1 and dim2