001package org.hl7.fhir.r4.utils; 002 003/* 004 Copyright (c) 2011+, HL7, Inc. 005 All rights reserved. 006 007 Redistribution and use in source and binary forms, with or without modification, 008 are permitted provided that the following conditions are met: 009 010 * Redistributions of source code must retain the above copyright notice, this 011 list of conditions and the following disclaimer. 012 * Redistributions in binary form must reproduce the above copyright notice, 013 this list of conditions and the following disclaimer in the documentation 014 and/or other materials provided with the distribution. 015 * Neither the name of HL7 nor the names of its contributors may be used to 016 endorse or promote products derived from this software without specific 017 prior written permission. 018 019 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 022 IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 023 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 024 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 025 PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 026 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 027 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 028 POSSIBILITY OF SUCH DAMAGE. 029 030 */ 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.hl7.fhir.exceptions.FHIRException; 036import org.hl7.fhir.utilities.Utilities; 037 038public class SnomedExpressions { 039 040 public class Base { 041 private int stop; 042 private int start; 043 044 public int getStop() { 045 return stop; 046 } 047 048 public void setStop(int stop) { 049 this.stop = stop; 050 } 051 052 public int getStart() { 053 return start; 054 } 055 056 public void setStart(int start) { 057 this.start = start; 058 } 059 } 060 061 public class Concept extends Base { 062 private long reference; 063 private String code; 064 private String description; 065 private String literal; 066 private String decimal; 067 068 public long getReference() { 069 return reference; 070 } 071 072 public void setReference(long reference) { 073 this.reference = reference; 074 } 075 076 public String getCode() { 077 return code; 078 } 079 080 public void setCode(String code) { 081 this.code = code; 082 } 083 084 public String getDescription() { 085 return description; 086 } 087 088 public void setDescription(String description) { 089 this.description = description; 090 } 091 092 public String getLiteral() { 093 return literal; 094 } 095 096 public void setLiteral(String literal) { 097 this.literal = literal; 098 } 099 100 public String getDecimal() { 101 return decimal; 102 } 103 104 public void setDecimal(String decimal) { 105 this.decimal = decimal; 106 } 107 108 @Override 109 public String toString() { 110 if (code != null) 111 return code; 112 else if (decimal != null) 113 return "#" + decimal; 114 else if (literal != null) 115 return "\"" + literal + "\""; 116 else 117 return ""; 118 } 119 } 120 121 public enum ExpressionStatus { 122 Unknown, Equivalent, SubsumedBy; 123 } 124 125 public class Expression extends Base { 126 private List<RefinementGroup> refinementGroups = new ArrayList<RefinementGroup>(); 127 private List<Refinement> refinements = new ArrayList<Refinement>(); 128 private List<Concept> concepts = new ArrayList<Concept>(); 129 private ExpressionStatus status; 130 131 public ExpressionStatus getStatus() { 132 return status; 133 } 134 135 public void setStatus(ExpressionStatus status) { 136 this.status = status; 137 } 138 139 public List<RefinementGroup> getRefinementGroups() { 140 return refinementGroups; 141 } 142 143 public List<Refinement> getRefinements() { 144 return refinements; 145 } 146 147 public List<Concept> getConcepts() { 148 return concepts; 149 } 150 151 @Override 152 public String toString() { 153 StringBuilder b = new StringBuilder(); 154 if (status == ExpressionStatus.Equivalent) 155 b.append("==="); 156 else if (status == ExpressionStatus.SubsumedBy) 157 b.append("<<<"); 158 boolean first = true; 159 for (Concept concept : concepts) { 160 if (first) 161 first = false; 162 else 163 b.append(','); 164 b.append(concept.toString()); 165 } 166 for (Refinement refinement : refinements) { 167 if (first) 168 first = false; 169 else 170 b.append(','); 171 b.append(refinement.toString()); 172 } 173 for (RefinementGroup refinementGroup : refinementGroups) { 174 if (first) 175 first = false; 176 else 177 b.append(','); 178 b.append(refinementGroup.toString()); 179 } 180 return b.toString(); 181 } 182 } 183 184 public class Refinement extends Base { 185 private Concept name; 186 private Expression value; 187 188 public Concept getName() { 189 return name; 190 } 191 192 public void setName(Concept name) { 193 this.name = name; 194 } 195 196 public Expression getValue() { 197 return value; 198 } 199 200 public void setValue(Expression value) { 201 this.value = value; 202 } 203 204 @Override 205 public String toString() { 206 return name.toString() + "=" + value.toString(); 207 } 208 } 209 210 public class RefinementGroup extends Base { 211 private List<Refinement> refinements = new ArrayList<Refinement>(); 212 213 public List<Refinement> getRefinements() { 214 return refinements; 215 } 216 217 @Override 218 public String toString() { 219 StringBuilder b = new StringBuilder(); 220 boolean first = true; 221 for (Refinement refinement : refinements) { 222 if (first) 223 first = false; 224 else 225 b.append(','); 226 b.append(refinement.toString()); 227 } 228 return b.toString(); 229 } 230 } 231 232 private static final int MAX_TERM_LIMIT = 1024; 233 234 private String source; 235 private int cursor; 236 237 private Concept concept() throws FHIRException { 238 Concept res = new Concept(); 239 res.setStart(cursor); 240 ws(); 241 if (peek() == '#') 242 res.decimal = decimal(); 243 else if (peek() == '"') 244 res.literal = stringConstant(); 245 else 246 res.code = conceptId(); 247 ws(); 248 if (gchar('|')) { 249 ws(); 250 res.description = term().trim(); 251 ws(); 252 fixed('|'); 253 ws(); 254 } 255 res.setStop(cursor); 256 return res; 257 } 258 259 private void refinements(Expression expr) throws FHIRException { 260 boolean n = true; 261 while (n) { 262 if (peek() != '{') 263 expr.refinements.add(attribute()); 264 else 265 expr.refinementGroups.add(attributeGroup()); 266 ws(); 267 n = gchar(','); 268 ws(); 269 } 270 } 271 272 private RefinementGroup attributeGroup() throws FHIRException { 273 RefinementGroup res = new RefinementGroup(); 274 fixed('{'); 275 ws(); 276 res.setStart(cursor); 277 res.refinements.add(attribute()); 278 while (gchar(',')) 279 res.refinements.add(attribute()); 280 res.setStop(cursor); 281 ws(); 282 fixed('}'); 283 ws(); 284 return res; 285 } 286 287 private Refinement attribute() throws FHIRException { 288 Refinement res = new Refinement(); 289 res.setStart(cursor); 290 res.name = attributeName(); 291 fixed('='); 292 res.value = attributeValue(); 293 ws(); 294 res.setStop(cursor); 295 return res; 296 } 297 298 private Concept attributeName() throws FHIRException { 299 Concept res = new Concept(); 300 res.setStart(cursor); 301 ws(); 302 res.code = conceptId(); 303 ws(); 304 if (gchar('|')) { 305 ws(); 306 res.description = term(); 307 ws(); 308 fixed('|'); 309 ws(); 310 } 311 res.setStop(cursor); 312 return res; 313 } 314 315 private Expression attributeValue() throws FHIRException { 316 Expression res; 317 ws(); 318 if (gchar('(')) { 319 res = expression(); 320 fixed(')'); 321 } else { 322 res = expression(); 323 } 324 return res; 325 } 326 327 private Expression expression() throws FHIRException { 328 Expression res = new Expression(); 329 res.setStart(cursor); 330 ws(); 331 res.concepts.add(concept()); 332 while (gchar('+')) 333 res.concepts.add(concept()); 334 if (gchar(':')) { 335 ws(); 336 refinements(res); 337 } 338 res.setStop(cursor); 339 return res; 340 } 341 342 private String conceptId() throws FHIRException { 343 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', 18)); 344 int i = 0; 345 while (peek() >= '0' && peek() <= '9') { 346 res.setCharAt(i, next()); 347 i++; 348 } 349 rule(i > 0, "Concept not found (next char = \"" + peekDisp() + "\", in '" + source + "')"); 350 return res.substring(0, i); 351 } 352 353 private String decimal() throws FHIRException { 354 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', MAX_TERM_LIMIT)); 355 int i = 0; 356 fixed('#'); 357 while ((peek() >= '0' && peek() <= '9') || peek() == '.') { 358 res.setCharAt(i, next()); 359 i++; 360 } 361 return res.substring(0, i); 362 } 363 364 private String term() { 365 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', MAX_TERM_LIMIT)); 366 int i = 0; 367 while (peek() != '|') { 368 res.setCharAt(i, next()); 369 i++; 370 } 371 return res.substring(0, i); 372 } 373 374 private void ws() { 375 while (Utilities.existsInList(peek(), ' ', '\t', '\r', 'n')) 376 next(); 377 } 378 379 private boolean gchar(char ch) { 380 boolean result = peek() == ch; 381 if (result) 382 next(); 383 return result; 384 } 385 386 private void fixed(char ch) throws FHIRException { 387 boolean b = gchar(ch); 388 rule(b, "Expected character \"" + ch + "\" but found " + peek()); 389 ws(); 390 } 391 392 private Expression parse() throws FHIRException { 393 Expression res = new Expression(); 394 res.setStart(cursor); 395 ws(); 396 if (peek() == '=') { 397 res.status = ExpressionStatus.Equivalent; 398 prefix('='); 399 } else if (peek() == '<') { 400 res.status = ExpressionStatus.SubsumedBy; 401 prefix('<'); 402 } 403 404 res.concepts.add(concept()); 405 while (gchar('+')) 406 res.concepts.add(concept()); 407 if (gchar(':')) { 408 ws(); 409 refinements(res); 410 } 411 res.setStop(cursor); 412 rule(cursor >= source.length(), "Found content (\"" + peekDisp() + "\") after end of expression"); 413 return res; 414 } 415 416 public static Expression parse(String source) throws FHIRException { 417 SnomedExpressions self = new SnomedExpressions(); 418 self.source = source; 419 self.cursor = 0; 420 return self.parse(); 421 } 422 423 private char peek() { 424 if (cursor >= source.length()) 425 return '\0'; 426 else 427 return source.charAt(cursor); 428 } 429 430 private String peekDisp() { 431 if (cursor >= source.length()) 432 return "[n/a: overrun]"; 433 else 434 return String.valueOf(source.charAt(cursor)); 435 } 436 437 private void prefix(char c) throws FHIRException { 438 fixed(c); 439 fixed(c); 440 fixed(c); 441 ws(); 442 } 443 444 private char next() { 445 char res = peek(); 446 cursor++; 447 return res; 448 } 449 450 private void rule(boolean test, String message) throws FHIRException { 451 if (!test) 452 throw new FHIRException(message + " at character " + Integer.toString(cursor)); 453 } 454 455 private String stringConstant() throws FHIRException { 456 StringBuffer res = new StringBuffer(Utilities.padLeft("", ' ', MAX_TERM_LIMIT)); 457 fixed('"'); 458 int i = 0; 459 while (peek() != '"') { 460 i++; 461 res.setCharAt(i, next()); 462 } 463 fixed('"'); 464 return res.substring(0, i); 465 } 466 467}