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.
6 comments:
Is a public enable() method really necessary? It seems like addUnitSupport() could take care of that for you (or possibly just enable as part of a static block in the UnitsDSL class).
Thanks for the comment. I think the enable() method is just a matter of taste. It actually modifies the metaclass on Number and Amount, which is 90% of the DSL. addUnitSupport() is the other 10% by adding support for assigning strings to Amount properties on a class. If you didn't do it until addUnitSupport() was called, then you wouldn't be able to do '3.m' anywhere until you had added unit support to a specific class. A static block would work as well if you knew you were always going to need the unit manipulation functionality. I just am hesitant about changing runtime behavior without some explicit invocation when I know I won't always need the extra functionality.
Yourcodeisunfortunatelynotthread-safe.
Betweentheifstatementtochecktheenablementandthesettingofthevariableanotherthreadcouldrun...
Your comment would be more useful had you used spaces.
The code does not need to be thread safe. The enable() method can run multiple times without issue. Each time it simply overwrites the previous metaclass methods with the new version. The isEnabled flag just tries to save you useless cycles. But you are correct in your assessment that under some conditions the enable() method may run more than once.
If you're worried about it, check out the other two comments on the post for other options.
aare you in antartida?
Not anymore. I arrived home in December.
Post a Comment