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