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.