package org.psicat.model
import org.jscience.physics.amount.*
import javax.measure.unit.*
/**
* A helper class for setting up the Units DSL
*/
class UnitDSL {
private static boolean isEnabled = false;
private UnitDSL() { /* singleton */ }
/**
* Initialize the Units DSL.
*/
static enable() {
// only initialize once
if (isEnabled) return
// mark ourselves as initialized
isEnabled = true
// enable inheritance on EMC
ExpandoMetaClass.enableGlobally()
// transform number properties into an mount of a given unit represented by the property
Number.metaClass.getProperty = { String symbol -> Amount.valueOf(delegate, Unit.valueOf(symbol)) }
// define opeartor overloading, as JScience doesn\'t use the same operation names as Groovy
Amount.metaClass.static.valueOf = { Number number, String unit -> Amount.valueOf(number, Unit.valueOf(unit)) }
Amount.metaClass.multiply = { Number factor -> delegate.times(factor) }
Number.metaClass.multiply = { Amount amount -> amount.times(delegate) }
Number.metaClass.div = { Amount amount -> amount.inverse().times(delegate) }
Amount.metaClass.div = { Number factor -> delegate.divide(factor) }
Amount.metaClass.div = { Amount factor -> delegate.divide(factor) }
Amount.metaClass.power = { Number factor -> delegate.pow(factor) }
Amount.metaClass.negative = { -> delegate.opposite() }
// for unit conversions
Amount.metaClass.to = { Amount amount -> delegate.to(amount.unit) }
Amount.metaClass.to = { String unit -> delegate.to(Unit.valueOf(unit)) }
}
/**
* Add Units support to the specified class.
*/
static addUnitSupport(clazz) {
clazz.metaClass.setProperty = { String name, value ->
def metaProperty = clazz.metaClass.getMetaProperty(name)
if (metaProperty) {
if (metaProperty.type == Amount.class && value instanceof String) {
metaProperty.setProperty(delegate, Amount.valueOf(value))
} else {
metaProperty.setProperty(delegate, value)
}
}
}
}
/**
* Remove Units support from the specified class.
*/
static removeUnitSupport(clazz) {
GroovySystem.metaClassRegistry.removeMetaClass(clazz)
}
}
To enable the DSL, you have to call
UnitDSL.enable()
. This adds a few methods to the metaclasses on Number
and Amount
. The majority of the code in enable() is a straight cut and paste job from Guillaume's article. I did add two methods. The first:
Amount.metaClass.static.valueOf = { Number number, String unit -> Amount.valueOf(number, Unit.valueOf(unit)) }
allows you to create an Amount using a Number and a String, e.g.
Amount.valueOf(1.5, "cm")
.The other new method:
Amount.metaClass.to = { String unit -> delegate.to(Unit.valueOf(unit)) }
allows conversions with the unit specified as a String, e.g.
1.5.m.to("cm")
The final enhancement I added was to create a addUnitSupport(clazz) method. This method overrides the setProperty() method of the passed class to support setting Amount properties as strings. All assignments in the following scenario are valid:
class Foo {
Amount bar
}
// test
UnitDSL.enable()
UnitDSL.addUnitSupport(Foo)
def foo = new Foo()
foo.bar = Amount.valueOf(3, SI.METER)
foo.bar = Amount.valueOf(3, "m")
foo.bar = Amount.valueOf("3m")
foo.bar = 3.m
foo.bar = "3m"
To use this code, you'll have to grab the latest JScience release.