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