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