001package org.hl7.fhir.utilities.ucum; 002 003/******************************************************************************* 004 * Crown Copyright (c) 2006 - 2014, Copyright (c) 2006 - 2014 Kestral Computing & Health Intersections. 005 * All rights reserved. This program and the accompanying materials 006 * are made available under the terms of the Eclipse Public License v1.0 007 * which accompanies this distribution, and is available at 008 * http://www.eclipse.org/legal/epl-v10.html 009 * 010 * Contributors: 011 * Kestral Computing P/L - initial implementation 012 * Health Intersections - ongoing maintenance 013 *******************************************************************************/ 014 015import java.io.File; 016import java.io.InputStream; 017import java.util.ArrayList; 018import java.util.HashSet; 019import java.util.List; 020import java.util.Set; 021 022import org.hl7.fhir.exceptions.UcumException; 023import org.hl7.fhir.utilities.Utilities; 024import org.hl7.fhir.utilities.ucum.special.Registry; 025 026 027/** 028 * implements UCUM services. Applications must provide a copy of 029 * ucum-essence.xml as either a stream or a file name to create 030 * the services. 031 * 032 * the provided ucum-essence.xml must be released on 25 Apr 2008 033 * or more recent. Note that if the ucum-essence.xml file does not 034 * contain a release date on an attribute of the root element, it 035 * is not more recent than this date (Gunther added it on this date for 036 * this usage, thanks) 037 * 038 * See UcumService for documentation concerning the services this class provides 039 * 040 * @author Grahame Grieve 041 * 042 */ 043public class UcumEssenceService implements UcumService { 044 045 public static final String UCUM_OID = "2.16.840.1.113883.6.8"; 046 047 private UcumModel model; 048 private Registry handlers = new Registry(); 049 050 /** 051 * Create an instance of Ucum services. Stream must point to a 052 * valid ucum-essence file (see class documentation) 053 * @throws UcumException 054 */ 055 public UcumEssenceService(InputStream stream) throws UcumException { 056 super(); 057 assert stream != null : paramError("factory", "stream", "must not be null"); 058 try { 059 model = new DefinitionParser().parse(stream); 060 } catch (Exception e) { 061 throw new UcumException(e); 062 } 063 } 064 065 /** 066 * Create an instance of Ucum services. filename must point to a 067 * valid ucum-essence file (see class documentation) 068 * @throws UcumException 069 */ 070 public UcumEssenceService(String filename) throws UcumException { 071 super(); 072 assert new File(filename).exists() : paramError("factory", "file", "must exist"); 073 try { 074 model = new DefinitionParser().parse(filename); 075 } catch (Exception e) { 076 throw new UcumException(e); 077 } 078 } 079 080 private String paramError(String method, String param, String msg) { 081 return getClass().getName()+"."+method+"."+param+" is not acceptable: "+msg; 082 } 083 084 085 @Override 086 public UcumVersionDetails ucumIdentification() { 087 return new UcumVersionDetails(model.getRevisionDate(), model.getVersion()); 088 } 089 090 091 /* (non-Javadoc) 092 * @see org.eclipse.ohf.ucum.UcumServiceEx#getModel() 093 */ 094 @Override 095 public UcumModel getModel() { 096 return model; 097 } 098 099 100 /* (non-Javadoc) 101 * @see org.eclipse.ohf.ucum.UcumServiceEx#search(org.eclipse.ohf.ucum.model.ConceptKind, java.lang.String, boolean) 102 */ 103 @Override 104 public List<Concept> search(ConceptKind kind, String text, boolean isRegex) { 105 assert checkStringParam(text) : paramError("search", "text", "must not be null or empty"); 106 return new Search().doSearch(model, kind, text, isRegex); 107 } 108 109 110 /* (non-Javadoc) 111 * @see org.eclipse.ohf.ucum.UcumServiceEx#validateUCUM() 112 */ 113 @Override 114 public List<String> validateUCUM() { 115 return new UcumValidator(model, handlers).validate(); 116 } 117 118 /* (non-Javadoc) 119 * @see org.eclipse.ohf.ucum.UcumServiceEx#getProperties() 120 */ 121 @Override 122 public Set<String> getProperties() { 123 Set<String> result = new HashSet<String>(); 124 for (DefinedUnit unit : model.getDefinedUnits()) { 125 result.add(unit.getProperty()); 126 } 127 return result; 128 } 129 130 /* (non-Javadoc) 131 * @see org.eclipse.ohf.ucum.UcumServiceEx#validate(java.lang.String) 132 */ 133 @Override 134 public String validate(String unit) { 135 assert unit != null : paramError("validate", "unit", "must not be null"); 136 try { 137 new ExpressionParser(model).parse(unit); 138 return null; 139 } catch (Exception e) { 140 return e.getMessage(); 141 } 142 } 143 144 /* (non-Javadoc) 145 * @see org.eclipse.ohf.ucum.UcumServiceEx#validateInProperty(java.lang.String, java.lang.String) 146 */ 147 @Override 148 public String validateInProperty(String unit, String property) { 149 assert checkStringParam(unit) : paramError("validate", "unit", "must not be null or empty"); 150 assert checkStringParam(property) : paramError("validateInProperty", "property", "must not be null or empty"); 151 try { 152 Term term = new ExpressionParser(model).parse(unit); 153 Canonical can = new Converter(model, handlers).convert(term); 154 String cu = new ExpressionComposer().compose(can, false); 155 if (can.getUnits().size() == 1) { 156 if (property.equals(can.getUnits().get(0).getBase().getProperty())) 157 return null; 158 else 159 return "unit "+unit+" is of the property type "+can.getUnits().get(0).getBase().getProperty()+" ("+cu+"), not "+property+" as required."; 160 } 161 // defined special case 162 if ("concentration".equals(property) && ("g/L".equals(cu) || "mol/L".equals(cu))) 163 return null; 164 165 return "unit "+unit+" has the base units "+cu+", and are not from the property "+property+" as required."; 166 } catch (Exception e) { 167 return e.getMessage(); 168 } 169 } 170 171 /* (non-Javadoc) 172 * @see org.eclipse.ohf.ucum.UcumServiceEx#validateCanonicalUnits(java.lang.String, java.lang.String) 173 */ 174 @Override 175 public String validateCanonicalUnits(String unit, String canonical) { 176 assert checkStringParam(unit) : paramError("validate", "unit", "must not be null or empty"); 177 assert checkStringParam(canonical) : paramError("validateCanonicalUnits", "canonical", "must not be null or empty"); 178 try { 179 Term term = new ExpressionParser(model).parse(unit); 180 Canonical can = new Converter(model, handlers).convert(term); 181 String cu = new ExpressionComposer().compose(can, false); 182 if (!canonical.equals(cu)) 183 return "unit "+unit+" has the base units "+cu+", not "+canonical+" as required."; 184 return null; 185 } catch (Exception e) { 186 return e.getMessage(); 187 } 188 } 189 190 /** 191 * given a unit, return a formal description of what the units stand for using 192 * full names 193 * @param units the unit code 194 * @return formal description 195 * @throws UcumException 196 * @ 197 */ 198 @Override 199 public String analyse(String unit) throws UcumException { 200 if (Utilities.noString(unit)) 201 return "(unity)"; 202 assert checkStringParam(unit) : paramError("analyse", "unit", "must not be null or empty"); 203 Term term = new ExpressionParser(model).parse(unit); 204 return new FormalStructureComposer().compose(term); 205 } 206 207 /* (non-Javadoc) 208 * @see org.eclipse.ohf.ucum.UcumServiceEx#getCanonicalUnits(java.lang.String) 209 */ 210 @Override 211 public String getCanonicalUnits(String unit) throws UcumException { 212 assert checkStringParam(unit) : paramError("getCanonicalUnits", "unit", "must not be null or empty"); 213 try { 214 Term term = new ExpressionParser(model).parse(unit); 215 return new ExpressionComposer().compose(new Converter(model, handlers).convert(term), false); 216 } catch (Exception e) { 217 throw new UcumException("Error processing "+unit+": "+e.getMessage(), e); 218 } 219 } 220 221 /* (non-Javadoc) 222 * @see org.eclipse.ohf.ucum.UcumServiceEx#getDefinedForms(java.lang.String) 223 */ 224 @Override 225 public List<DefinedUnit> getDefinedForms(String code) throws UcumException { 226 assert checkStringParam(code) : paramError("getDefinedForms", "code", "must not be null or empty"); 227 List<DefinedUnit> result = new ArrayList<DefinedUnit>(); 228 BaseUnit base = model.getBaseUnit(code); 229 if (base != null) { 230 for (DefinedUnit unit : model.getDefinedUnits()) { 231 if (!unit.isSpecial() && code.equals(getCanonicalUnits(unit.getCode()))) 232 result.add(unit); 233 } 234 } 235 return result; 236 } 237 238 private boolean checkStringParam(String s) { 239 return s != null && !s.equals(""); 240 } 241 242 243 /* (non-Javadoc) 244 * @see org.eclipse.ohf.ucum.UcumServiceEx#getCanonicalForm(org.eclipse.ohf.ucum.UcumEssenceService.Pair) 245 */ 246 @Override 247 public Pair getCanonicalForm(Pair value) throws UcumException { 248 assert value != null : paramError("getCanonicalForm", "value", "must not be null"); 249 assert checkStringParam(value.getCode()) : paramError("getCanonicalForm", "value.code", "must not be null or empty"); 250 251 Term term = new ExpressionParser(model).parse(value.getCode()); 252 Canonical c = new Converter(model, handlers).convert(term); 253 if (value.getValue() == null) 254 return new Pair(null, new ExpressionComposer().compose(c, false)); 255 else 256 return new Pair(value.getValue().multiply(c.getValue()), new ExpressionComposer().compose(c, false)); 257 } 258 259 /* (non-Javadoc) 260 * @see org.eclipse.ohf.ucum.UcumServiceEx#convert(java.math.BigDecimal, java.lang.String, java.lang.String) 261 */ 262 @Override 263 public Decimal convert(Decimal value, String sourceUnit, String destUnit) throws UcumException { 264 assert value != null : paramError("convert", "value", "must not be null"); 265 assert checkStringParam(sourceUnit) : paramError("convert", "sourceUnit", "must not be null or empty"); 266 assert checkStringParam(destUnit) : paramError("convert", "destUnit", "must not be null or empty"); 267 268 if (sourceUnit.equals(destUnit)) 269 return value; 270 271 Canonical src = new Converter(model, handlers).convert(new ExpressionParser(model).parse(sourceUnit)); 272 Canonical dst = new Converter(model, handlers).convert(new ExpressionParser(model).parse(destUnit)); 273 String s = new ExpressionComposer().compose(src, false); 274 String d = new ExpressionComposer().compose(dst, false); 275 if (!s.equals(d)) 276 throw new UcumException("Unable to convert between units "+sourceUnit+" and "+destUnit+" as they do not have matching canonical forms ("+s+" and "+d+" respectively)"); 277 Decimal canValue = value.multiply(src.getValue()); 278// System.out.println(value.toPlainString()+sourceUnit+" =("+src.getValue().toPlainString()+")= "+ 279// canValue.toPlainString()+s+" =("+dst.getValue().toPlainString()+")= "+ 280// canValue.divide(dst.getValue())+destUnit); 281 return canValue.divide(dst.getValue()); 282 } 283 284 @Override 285 public Pair multiply(Pair o1, Pair o2) throws UcumException { 286 Pair res = new Pair(o1.getValue().multiply(o2.getValue()), o1.getCode() +"."+o2.getCode()); 287 return getCanonicalForm(res); 288 } 289 290 @Override 291 public String getCommonDisplay(String code) { 292 //TODO: improvements 293 return code.replace("[", "").replace("]", ""); 294 } 295 296 @Override 297 public boolean isComparable(String units1, String units2) throws UcumException { 298 if (units1 == null) 299 return false; 300 if (units2 == null) 301 return false; 302 303 String u1 = getCanonicalUnits(units1); 304 String u2 = getCanonicalUnits(units2); 305 return u1.equals(u2); 306 } 307 308}