/*
* component: "openEHR Reference Implementation"
* description: "Class SimpleMeasurementService"
* keywords: "support"
*
* author: "Rong Chen <rong@acode.se>"
* support: "Acode HB <support@acode.se>"
* copyright: "Copyright (c) 2007 Acode HB, Sweden"
* license: "See notice at bottom of class"
*
* file: "$URL$"
* revision: "$LastChangedRevision$"
* last_change: "$LastChangedDate$"
*/
package org.openehr.rm.support.measurement;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.measure.Measure;
import javax.measure.converter.ConversionException;
import javax.measure.unit.Unit;
/**
* Simple implementation of measurement information service
*
* @author Rong Chen
* @version 1.0
*
*/
public class SimpleMeasurementService implements MeasurementService {
/**
*
*/
private static final long serialVersionUID = 1L;
public static MeasurementService getInstance() {
return soleInstance;
}
private static final SimpleMeasurementService soleInstance =
new SimpleMeasurementService();
private SimpleMeasurementService() {
}
/** This is a list of commonly used UCUM codes in health care.
* Cp. e.g. http://www.hl7.de/download/documents/ucum/ucumdata.html
* and http://unitsofmeasure.org/ucum.html and http://unitsofmeasure.org/ucum-essence.xml
*/
private static Set<String> commonUCUMCodes = new HashSet<String>( Arrays.asList(
"%", "/uL", "[iU]/L", "10*3/uL", "10*6/uL", "fL", "g/dL", "g/L", "g/mL", "kPa", "m[iU]/mL", "meq/L", "mg/dL", "mm[Hg]", "mmol/kg", "mmol/L", "mosm/kg", "ng/mL",
"nmol/L", "pg", "pg/mL", "pmol/L", "U/L", "u[iU]/mL", "ug/dL", "ug/L", "ug/mL", "[lg]", "10*6/{Specimen}", "/{tot}", "10*3", "10*3.{RBC}", "10*5", "10*6", "10*8", "%",
"{Relative}%", "%{Total}", "%{0to3Hours}", "/10*10", "/10*6", "/10*9", "/10*12", "%{Normal}", "%{SpermMotility}", "%{Positive}", "%{FetalErythrocytes}",
"%{OfLymphocytes}", "%{ofBacteria}", "%{OfWBCs}", "%{Abnormal}", "%{EosSeen}", "%{Hemolysis}", "%{Blockade}", "%/100{WBC}", "%{Binding}",
"%{TotalProtein}", "%{Bound}", "%{Hemoglobin}", "%{HemoglobinSaturation}", "%{Carboxyhemoglobin}", "%{HemoglobinA1C}", "%{Excretion}", "%{Uptake}", "ug/ng",
"ng/mg", "ng/mg{Protein}", "ug/mg", "ug/mg{Cre}", "mg/mg", "mg/mg{Cre}", "ng/g", "ng/g{Cre}", "ug/g", "ug/[100]g", "ug/g{DryWeight}", "ug/g{Cre}", "ug/g{Hgb}",
"mg/g", "mg/g{Cre}", "g/g", "ng/kg", "ug/kg", "mg/kg", "g/kg", "g/[100]g", "g/g{Cre}", "pmol/umol", "nmol/mmol", "nmol/mmol{Cre}", "nmol/mol", "umol/mol", "mmol/mol",
"mmol/mol{Cre}", "umol/mol{Cre}", "eq/umol", "eq/mmol", "{BoneCollagen}eq/mmol{Cre}", "{BoneCollagen}eq/umol{Cre}", "%{vol}", "%{Oxygen}", "mL/dL",
"%{NormalPooledPlasma}", "%{Activity}", "%{BasalActivity}", "%{Inhibition}", "/{Entity}", "/100{WBC}", "/100", "/100{Spermatozoa}", "/10*12{rbc}", "deg", "/[arb`U]",
"u[iU]", "[iU]", "10*6.[iU]", "[in_i]", "[ft_i]", "[yd_i]", "[fth_i]", "[mi_i]", "[nmi_i]", "[Ch]", "fm", "pm", "nm", "um", "mm", "cm", "dm", "m", "km", "[gr]", "[oz_av]", "[oz_tr]",
"[lb_av]", "[ston_av]", "[dr_av]", "fg", "pg", "ng", "ug", "ug/{TotalVolume}", "ug/{Specimen}", "mg", "mg/{Volume}", "mg/{TotalVolume}", "g", "g/{TotalWeight}", "dg", "cg",
"kg", "t", "pg/mm", "K", "Cel", "[degF]", "K/W", "ps", "ns", "us", "ms", "s", "ks", "Ms", "min", "h", "d", "wk", "mo", "a", "eq", "ueq", "meq", "meq/{Specimen}", "mol",
"mmol", "mmol/{TotalVolume}", "fmol", "pmol", "umol", "nmol", "mosm", "meq/m2", "mmol/m2", "[sin_i]", "[sft_i]", "[syd_i]", "mm2", "cm2", "m2", "[foz_us]", "[cin_i]",
"[cup_us]", "[pt_us]", "[qt_us]", "[gal_us]", "[fdr_us]", "fL", "pL", "nL", "uL", "mL", "mL/{h`b}", "L", "dL", "cL", "kL", "hL", "L.s2/s", "/mg", "/g", "/g{creat}", "/g{HGB}",
"/g{tot`nit}", "/g{tot`prot}", "/g{wet`tis}", "/kg", "/kg{body`wt}", "fmol/mg", "nmol/mg", "umol/mg", "umol/mg{Cre}", "mol/kg", "fmol/g", "nmol/g", "nmol/g{Cre}", "umol/g",
"umol/g{Cre}", "umol/g{Hgb}", "mmol/g", "mmol/kg", "osm/kg", "mosm/kg", "meq/g", "meq/g{Cre}", "meq/kg", "[iU]/g", "[iU]/g{Hgb}", "{Ehrlich_U}/100g", "[iU]/kg",
"umol/min/g", "mU/g", "mU/g{Hgb}", "U/g", "U/g{Hgb}", "U/g{Cre}", "mU/mg{Cre}", "mU/mg", "kU/g", "kat/kg", "mL/kg", "L/kg", "kCal/[oz_av]", "/m2", "g/m2", "kg/m2",
"ug/m2", "mg/m2", "ng/m2", "g.m", "g.m/{hb}", "g.m/({hb}.m2)", "kg/mol", "/uL", "{Cells}/uL", "{rbc}/uL", "10*3/uL", "10*6/uL", "10*9/uL", "/mL", "{Spermatozoa}/mL",
"{Copies}/mL", "10*3/mL", "10*3{Copies}/mL", "10*6/mL", "10*9/mL", "{cfu}/mL", "/dL", "/L", "10*3/L", "10*6/L", "10*12/L", "10*9/L", "pg/mL", "ng/mL", "ng/mL{rbc}",
"ug/mL", "mg/mL", "g/mL", "pg/dL", "ng/dL", "ug/dL", "ug/dL{rbc}", "mg/dL", "mg{Phenylketones}/dL", "g/dL", "ng/L", "pg/L", "ug/L", "mg/L", "g/L", "kg/L", "mg/m3",
"kg/m3", "fmol/mL", "pmol/mL", "nmol/mL", "umol/mL", "mol/mL", "pmol/dL", "nmol/dL", "umol/dL", "mmol/dL", "mmol/L", "pmol/L", "nmol/L", "umol/L", "mol/L",
"mol/m3", "ueq/mL", "meq/mL", "eq/mL", "{AHG}eq/mL", "10*6.eq/mL", "ueq/L", "meq/L", "eq/L", "meq/dL", "mosm/L", "osm/L", "u[iU]/mL", "m[iU]/mL",
"{IgGPhospholipid}U/mL", "{IgMPhospholipid}U/mL", "{ComplementCh50}U/mL", "{IgAPhospholipid}U/mL", "{Elisa_U}/mL", "[iU]/mL", "k[iU]/mL", "[iU]/dL",
"{Ehrlich_U}/dL", "m[iU]/L", "[iU]/L", "[pH]",
"osm", "/wk", "ml/wk", "mL/wk", "g/wk", "mg/wk", "B", "dB", "cm[H2O]", "mm[H20]", "10*6/mm3", "U", "mU",
// both [iU] and [IU] are correct in case-sensitive UCUM variant
"[IU]/L","m[IU]/mL", "u[IU]/mL", "u[IU]", "[IU]", "10*6.[IU]", "[IU]/g", "[IU]/g{Hgb}", "[IU]/kg", "u[iU]/mL", "m[iU]/mL", "[iU]/mL", "k[iU]/mL", "[iU]/dL", "m[iU]/L", "[iU]/L",
// We also need to add the lower-case variant for L because both l and L are valid for litre in the case-sensitive UCUM variant
"/ul", "[iU]/l", "10*3/ul", "10*6/ul", "fl", "g/dl", "g/l", "g/ml", "m[iU]/ml", "meq/l", "mg/dl", "mmol/l","ng/ml",
"nmol/l","pg/ml", "pmol/l", "U/l", "u[iU]/ml", "ug/dl", "ug/l", "ug/ml", "ml/dl",
"fl", "pl", "nl", "ul", "ml", "ml/{h`b}", "l", "dl", "cl", "kl", "hl", "l.s2/s",
"ml/kg", "l/kg", "/ul", "{Cells}/ul", "{rbc}/ul", "10*3/ul", "10*6/ul", "10*9/ul", "/ml", "{Spermatozoa}/ml",
"{Copies}/ml", "10*3/ml", "10*3{Copies}/ml", "10*6/ml", "10*9/ml", "{cfu}/ml", "/dl", "/l", "10*3/l", "10*6/l", "10*12/l", "10*9/l", "pg/ml", "ng/ml", "ng/ml{rbc}",
"ug/ml", "mg/ml", "g/ml", "pg/dl", "ng/dl", "ug/dl", "ug/dl{rbc}", "mg/dl", "mg{Phenylketones}/dl", "g/dl", "ng/l", "pg/l", "ug/l", "mg/l", "g/l", "kg/l",
"fmol/ml", "pmol/ml", "nmol/ml", "umol/ml", "mol/ml", "pmol/dl", "nmol/dl", "umol/dl", "mmol/dl", "mmol/l", "pmol/l", "nmol/l", "umol/l", "mol/l",
"ueq/ml", "meq/ml", "eq/ml", "{AHG}eq/ml", "10*6.eq/ml", "ueq/l", "meq/l", "eq/l", "meq/dl", "mosm/l", "osm/l", "u[iU]/ml", "m[iU]/ml",
"{IgGPhospholipid}U/ml", "{IgMPhospholipid}U/ml", "{ComplementCh50}U/ml", "{IgAPhospholipid}U/ml", "{Elisa_U}/ml", "[iU]/ml", "k[iU]/ml", "[iU]/dl",
"{Ehrlich_U}/dl", "m[iU]/l", "[iU]/l",
"1/wk", "[oz_av]/wk"
));
/**
* Returns True if the units string according to
* the HL7 UCUM specification.
* Note that this implementation currently assumes case-sensitive UCUM format.
*
* @param units
* @return true if units valid
* @throws IllegalArgumentException if units null
*/
public boolean isValidUnitsString(String units) {
if(units == null) {
throw new IllegalArgumentException("units null");
}
SimpleUCUMValidator ucumVal = new SimpleUCUMValidator();
if (ucumVal.isValidUnitsString(units)) {
return true;
} else {
return false;
}
// Unfortunately the UCUM support in the library javax.measure.unit uses a very outdated version of UCUM.
// E.g. mm[Hg], osm, eq are missing in there. Therefore we first check if the unit is a UCUM unit commonly used in health care.
/* if (commonUCUMCodes.contains(units)) {
return true;
}
// Only if it is not in this list, we continue to check with the provided library if the UCUM unit can be constructed.
Unit<?> unit = null;
try {
// This is equivalent to this UCUMFormat.getCaseSensitiveInstance().parseObject(units, new ParsePosition(0));
// That means it is the case-sensitive variant of UCUM that is used here.
unit = Unit.valueOf(units);
} catch(java.lang.IllegalArgumentException e) {
}
*/
// If we want to support the case-insensitive UCUM variant as well, we can do so via this:
// Unit unitCI = UCUMFormat.getCaseInsensitiveInstance().parseObject(units, new ParsePosition(0));
/* System.out.println("===============================");
if (unit != null) {
System.out.println("Unit parsed CS: "+units +"->"+UCUMFormat.getCaseSensitiveInstance().format(unit));
} else {
System.out.println("Unit NOT parsed CS: "+ units);
}
if (unitCI != null){
System.out.println("Unit parsed CI: "+units +"->"+UCUMFormat.getCaseInsensitiveInstance().format(unitCI));
} else {
System.out.println("Unit NOT parsed CI: "+ units);
}
*/
// return unit!= null;
}
/**
* Return True if two units strings correspond to the same
* measured property.
*
* @param units1
* @param units2
* @return true if two units equal
* @throws IllegalArgumentException if units1 or units2 null
*/
public boolean unitsEquivalent(String units1, String units2) {
if(units1 == null) {
throw new IllegalArgumentException("units1 null");
}
if(units2 == null) {
throw new IllegalArgumentException("units2 null");
}
if (units1.equals(units2)) {
return true;
}
// SG: Not too sure that this is actually correct...
// a) For case-sensitive UCUM, mg and Mg are quite different, but would be accepted as the same (milli vs. mega)
// Also, the upper-case G in the case-sensitive UCUM refers to Gauss' constant
// b) Would a unit be equivalent if they are formally the same, e.g A[mpere] = C[oulomb]/s[econd] are formally equivalent
Unit<?> unit1 = Unit.valueOf(units1);
Unit<?> unit2 = Unit.valueOf(units2);
try {
return (unit1.getConverterToAny(unit2).convert(1) ==1);
} catch (UnsupportedOperationException e) {
return false;
} catch (ConversionException e) {
// Units not even compatible
return false;
}
// return units1.equalsIgnoreCase(units2);
}
/**
* Return True if two units strings are comparable.
*
* @param units1
* @param units2
* @return true if two units comparable
* @throws IllegalArgumentException if units1 or units2 null
*/
public boolean unitsComparable(String units1, String units2) {
if (unitsEquivalent(units1, units2)){
return true;
}else{
Unit<?> u1 = Unit.valueOf(units1);
Unit<?> u2 = Unit.valueOf(units2);
return u1.isCompatible(u2);
}
}
/**
* Comparison between two measures.
*
* @param units1
* @param units2
* @return a negative integer, zero, or a positive integer as this measure
* is less than, equal to, or greater than the specified measurable
* quantity.
* @throws IllegalArgumentException if units1, units2 null or not comparable
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public int compare(String units1, Double value1, String units2, Double value2) {
if(value1 == null) {
throw new IllegalArgumentException("value1 null");
}
if(value2 == null) {
throw new IllegalArgumentException("value2 null");
}
if (unitsEquivalent(units1, units2)){
return value1.compareTo(value2);
}else{
Unit<?> unit1 = Unit.valueOf(units1);
Unit<?> unit2 = Unit.valueOf(units2);
if (!unit1.isCompatible(unit2)){
throw new IllegalArgumentException("units '"+units1+"' is not comparable to '"+units2+"'");
}
Measure measure1 = Measure.valueOf(value1, unit1);
Measure measure2 = Measure.valueOf(value2, unit2);
return measure1.compareTo(measure2);
}
}
}
/*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the 'License'); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an 'AS IS' basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is SimpleMeasurementService.java
*
* The Initial Developer of the Original Code is Rong Chen.
* Portions created by the Initial Developer are Copyright (C) 2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Software distributed under the License is distributed on an 'AS IS' basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* ***** END LICENSE BLOCK *****
*/