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}