001package org.hl7.fhir.r4.utils; 002 003import java.util.ArrayList; 004import java.util.List; 005 006import org.hl7.fhir.exceptions.FHIRException; 007import org.hl7.fhir.utilities.Utilities; 008 009public class SnomedExpressions { 010 011 public class Base { 012 private int stop; 013 private int start; 014 public int getStop() { 015 return stop; 016 } 017 public void setStop(int stop) { 018 this.stop = stop; 019 } 020 public int getStart() { 021 return start; 022 } 023 public void setStart(int start) { 024 this.start = start; 025 } 026 } 027 028 public class Concept extends Base { 029 private long reference; 030 private String code; 031 private String description; 032 private String literal; 033 private String decimal; 034 public long getReference() { 035 return reference; 036 } 037 public void setReference(long reference) { 038 this.reference = reference; 039 } 040 public String getCode() { 041 return code; 042 } 043 public void setCode(String code) { 044 this.code = code; 045 } 046 public String getDescription() { 047 return description; 048 } 049 public void setDescription(String description) { 050 this.description = description; 051 } 052 public String getLiteral() { 053 return literal; 054 } 055 public void setLiteral(String literal) { 056 this.literal = literal; 057 } 058 public String getDecimal() { 059 return decimal; 060 } 061 public void setDecimal(String decimal) { 062 this.decimal = decimal; 063 } 064 @Override 065 public String toString() { 066 if (code != null) 067 return code; 068 else if (decimal != null) 069 return "#"+decimal; 070 else if (literal != null) 071 return "\""+literal+"\""; 072 else 073 return ""; 074 } 075 } 076 077 public enum ExpressionStatus { 078 Unknown, Equivalent, SubsumedBy; 079 } 080 081 public class Expression extends Base { 082 private List<RefinementGroup> refinementGroups = new ArrayList<RefinementGroup>(); 083 private List<Refinement> refinements = new ArrayList<Refinement>(); 084 private List<Concept> concepts = new ArrayList<Concept>(); 085 private ExpressionStatus status; 086 public ExpressionStatus getStatus() { 087 return status; 088 } 089 public void setStatus(ExpressionStatus status) { 090 this.status = status; 091 } 092 public List<RefinementGroup> getRefinementGroups() { 093 return refinementGroups; 094 } 095 public List<Refinement> getRefinements() { 096 return refinements; 097 } 098 public List<Concept> getConcepts() { 099 return concepts; 100 } 101 @Override 102 public String toString() { 103 StringBuilder b = new StringBuilder(); 104 if (status == ExpressionStatus.Equivalent) 105 b.append("==="); 106 else if (status == ExpressionStatus.SubsumedBy) 107 b.append("<<<"); 108 boolean first = true; 109 for (Concept concept : concepts) { 110 if (first) first = false; else b.append(','); 111 b.append(concept.toString()); 112 } 113 for (Refinement refinement : refinements) { 114 if (first) first = false; else b.append(','); 115 b.append(refinement.toString()); 116 } 117 for (RefinementGroup refinementGroup : refinementGroups) { 118 if (first) first = false; else b.append(','); 119 b.append(refinementGroup.toString()); 120 } 121 return b.toString(); 122 } 123 } 124 125 public class Refinement extends Base { 126 private Concept name; 127 private Expression value; 128 public Concept getName() { 129 return name; 130 } 131 public void setName(Concept name) { 132 this.name = name; 133 } 134 public Expression getValue() { 135 return value; 136 } 137 public void setValue(Expression value) { 138 this.value = value; 139 } 140 141 @Override 142 public String toString() { 143 return name.toString()+"="+value.toString(); 144 } 145 } 146 147 public class RefinementGroup extends Base { 148 private List<Refinement> refinements = new ArrayList<Refinement>(); 149 150 public List<Refinement> getRefinements() { 151 return refinements; 152 } 153 154 @Override 155 public String toString() { 156 StringBuilder b = new StringBuilder(); 157 boolean first = true; 158 for (Refinement refinement : refinements) { 159 if (first) first = false; else b.append(','); 160 b.append(refinement.toString()); 161 } 162 return b.toString(); 163 } 164 } 165 166 private static final int MAX_TERM_LIMIT = 1024; 167 168 private String source; 169 private int cursor; 170 171 private Concept concept() throws FHIRException { 172 Concept res = new Concept(); 173 res.setStart(cursor); 174 ws(); 175 if (peek() == '#') 176 res.decimal = decimal(); 177 else if (peek() == '"') 178 res.literal = stringConstant(); 179 else 180 res.code = conceptId(); 181 ws(); 182 if (gchar('|')) { 183 ws(); 184 res.description = term().trim(); 185 ws(); 186 fixed('|'); 187 ws(); 188 } 189 res.setStop(cursor); 190 return res; 191 } 192 193 private void refinements(Expression expr) throws FHIRException { 194 boolean n = true; 195 while (n) { 196 if (peek() != '{') 197 expr.refinements.add(attribute()); 198 else 199 expr.refinementGroups.add(attributeGroup()); 200 ws(); 201 n = gchar(','); 202 ws(); 203 } 204 } 205 206 private RefinementGroup attributeGroup() throws FHIRException { 207 RefinementGroup res = new RefinementGroup(); 208 fixed('{'); 209 ws(); 210 res.setStart(cursor); 211 res.refinements.add(attribute()); 212 while (gchar(',')) 213 res.refinements.add(attribute()); 214 res.setStop(cursor); 215 ws(); 216 fixed('}'); 217 ws(); 218 return res; 219 } 220 221 private Refinement attribute() throws FHIRException { 222 Refinement res = new Refinement(); 223 res.setStart(cursor); 224 res.name = attributeName(); 225 fixed('='); 226 res.value = attributeValue(); 227 ws(); 228 res.setStop(cursor); 229 return res; 230 } 231 232 private Concept attributeName() throws FHIRException { 233 Concept res = new Concept(); 234 res.setStart(cursor); 235 ws(); 236 res.code = conceptId(); 237 ws(); 238 if (gchar('|')) { 239 ws(); 240 res.description = term(); 241 ws(); 242 fixed('|'); 243 ws(); 244 } 245 res.setStop(cursor); 246 return res; 247 } 248 249 private Expression attributeValue() throws FHIRException { 250 Expression res; 251 ws(); 252 if (gchar('(')) { 253 res = expression(); 254 fixed(')'); 255 } else { 256 res = expression(); 257 } 258 return res; 259 } 260 261 private Expression expression() throws FHIRException { 262 Expression res = new Expression(); 263 res.setStart(cursor); 264 ws(); 265 res.concepts.add(concept()); 266 while (gchar('+')) 267 res.concepts.add(concept()); 268 if (gchar(':')) { 269 ws(); 270 refinements(res); 271 } 272 res.setStop(cursor); 273 return res; 274 } 275 276 private String conceptId() throws FHIRException { 277 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', 18)); 278 int i = 0; 279 while (peek() >= '0' && peek() <= '9') { 280 res.setCharAt(i, next()); 281 i++; 282 } 283 rule(i > 0, "Concept not found (next char = \""+peekDisp()+"\", in '"+source+"')"); 284 return res.substring(0, i); 285 } 286 287 private String decimal() throws FHIRException { 288 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', MAX_TERM_LIMIT)); 289 int i = 0; 290 fixed('#'); 291 while ((peek() >= '0' && peek() <= '9') || peek() == '.') { 292 res.setCharAt(i, next()); 293 i++; 294 } 295 return res.substring(0, i); 296 } 297 298 private String term() { 299 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', MAX_TERM_LIMIT)); 300 int i = 0; 301 while (peek() != '|') { 302 res.setCharAt(i, next()); 303 i++; 304 } 305 return res.substring(0, i); 306 } 307 308 private void ws() { 309 while (Utilities.existsInList(peek(), ' ', '\t', '\r', 'n')) 310 next(); 311 } 312 313 private boolean gchar(char ch) { 314 boolean result = peek() == ch; 315 if (result) 316 next(); 317 return result; 318 } 319 320 private void fixed(char ch) throws FHIRException { 321 boolean b = gchar(ch); 322 rule(b, "Expected character \""+ch+"\" but found "+peek()); 323 ws(); 324 } 325 326 private Expression parse() throws FHIRException { 327 Expression res = new Expression(); 328 res.setStart(cursor); 329 ws(); 330 if (peek() == '=') { 331 res.status = ExpressionStatus.Equivalent; 332 prefix('='); 333 } else if (peek() == '<') { 334 res.status = ExpressionStatus.SubsumedBy; 335 prefix('<'); 336 } 337 338 res.concepts.add(concept()); 339 while (gchar('+')) 340 res.concepts.add(concept()); 341 if (gchar(':')) { 342 ws(); 343 refinements(res); 344 } 345 res.setStop(cursor); 346 rule(cursor >= source.length(), "Found content (\""+peekDisp()+"\") after end of expression"); 347 return res; 348 } 349 350 public static Expression parse(String source) throws FHIRException { 351 SnomedExpressions self = new SnomedExpressions(); 352 self.source = source; 353 self.cursor = 0; 354 return self.parse(); 355 } 356 357 private char peek() { 358 if (cursor >= source.length()) 359 return '\0'; 360 else 361 return source.charAt(cursor); 362 } 363 364 private String peekDisp() { 365 if (cursor >= source.length()) 366 return "[n/a: overrun]"; 367 else 368 return String.valueOf(source.charAt(cursor)); 369 } 370 371 private void prefix(char c) throws FHIRException { 372 fixed(c); 373 fixed(c); 374 fixed(c); 375 ws(); 376 } 377 378 private char next() { 379 char res = peek(); 380 cursor++; 381 return res; 382 } 383 384 private void rule(boolean test, String message) throws FHIRException { 385 if (!test) 386 throw new FHIRException(message+" at character "+Integer.toString(cursor)); 387 } 388 389 private String stringConstant() throws FHIRException { 390 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', MAX_TERM_LIMIT)); 391 fixed('"'); 392 int i = 0; 393 while (peek() != '"') { 394 i++; 395 res.setCharAt(i, next()); 396 } 397 fixed('"'); 398 return res.substring(0, i); 399 } 400 401}