001package org.hl7.fhir.r5.elementmodel;
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.io.BufferedInputStream;
035import java.io.IOException;
036import java.io.InputStream;
037import java.io.InputStreamReader;
038import java.io.OutputStream;
039import java.util.ArrayList;
040import java.util.List;
041
042import org.hl7.fhir.exceptions.DefinitionException;
043import org.hl7.fhir.exceptions.FHIRException;
044import org.hl7.fhir.exceptions.FHIRFormatError;
045import org.hl7.fhir.r5.context.IWorkerContext;
046import org.hl7.fhir.r5.formats.IParser.OutputStyle;
047import org.hl7.fhir.r5.model.StructureDefinition;
048
049/**
050 * This class provides special support for parsing v2 by the v2 logical model
051 * For the logical model, see the FHIRPath spec
052 *         
053 * @author Grahame Grieve
054 *
055 */
056public class VerticalBarParser extends ParserBase {
057
058  /**
059   * Delimiters for a message. Note that the application rarely needs to concern
060   * itself with this information; it mainly exists for internal use. However if
061   * a message is being written to a spec that calls for non-standard delimiters,
062   * the application can set them here.  
063   * 
064   * @author Grahame
065   *
066   */
067  public class Delimiters {
068
069    /**
070     * Hl7 defined default delimiter for a field
071     */
072    public final static char DEFAULT_DELIMITER_FIELD = '|';
073
074    /**
075     * Hl7 defined default delimiter for a component
076     */
077    public final static char  DEFAULT_DELIMITER_COMPONENT = '^';
078
079    /**
080     * Hl7 defined default delimiter for a subcomponent
081     */
082    public final static char  DEFAULT_DELIMITER_SUBCOMPONENT = '&';
083
084    /**
085     * Hl7 defined default delimiter for a repeat
086     */
087    public final static char  DEFAULT_DELIMITER_REPETITION = '~';
088
089    /**
090     * Hl7 defined default delimiter for an escape
091     */
092    public final static char  DEFAULT_CHARACTER_ESCAPE = '\\';
093
094
095    /**
096     * defined escape character for this message
097     */
098    private char escapeCharacter;
099
100    /**
101     * defined repetition character for this message
102     */
103      private char repetitionDelimiter;
104
105    /**
106     * defined field character for this message
107     */
108      private char fieldDelimiter;
109
110    /**
111     * defined subComponent character for this message
112     */
113      private char subComponentDelimiter;
114
115    /**
116     * defined component character for this message
117     */
118      private char componentDelimiter;
119
120      /**
121       * create
122       *
123       */
124    public Delimiters() {
125      super();
126      reset();
127    }
128
129    public boolean matches(Delimiters other) {
130      return escapeCharacter == other.escapeCharacter &&
131      repetitionDelimiter == other.repetitionDelimiter &&
132      fieldDelimiter == other.fieldDelimiter &&
133      subComponentDelimiter == other.subComponentDelimiter &&
134      componentDelimiter == other.componentDelimiter;
135    }
136    
137    /**
138     * get defined component character for this message
139     * @return
140     */
141    public char getComponentDelimiter() {
142      return componentDelimiter;
143    }
144
145    /**
146     * set defined component character for this message
147     * @param componentDelimiter
148     */
149    public void setComponentDelimiter(char componentDelimiter) {
150      this.componentDelimiter = componentDelimiter;
151    }
152
153    /**
154     * get defined escape character for this message
155     * @return
156     */
157    public char getEscapeCharacter() {
158      return escapeCharacter;
159    }
160
161    /**
162     * set defined escape character for this message
163     * @param escapeCharacter
164     */
165    public void setEscapeCharacter(char escapeCharacter) {
166      this.escapeCharacter = escapeCharacter;
167    }
168
169    /**
170     * get defined field character for this message
171     * @return
172     */
173    public char getFieldDelimiter() {
174      return fieldDelimiter;
175    }
176
177    /**
178     * set defined field character for this message
179     * @param fieldDelimiter
180     */
181    public void setFieldDelimiter(char fieldDelimiter) {
182      this.fieldDelimiter = fieldDelimiter;
183    }
184
185    /**
186     * get repeat field character for this message
187     * @return
188     */
189    public char getRepetitionDelimiter() {
190      return repetitionDelimiter;
191    }
192
193    /**
194     * set repeat field character for this message
195     * @param repetitionDelimiter
196     */
197    public void setRepetitionDelimiter(char repetitionDelimiter) {
198      this.repetitionDelimiter = repetitionDelimiter;
199    }
200
201    /**
202     * get sub-component field character for this message
203     * @return
204     */
205    public char getSubComponentDelimiter() {
206      return subComponentDelimiter;
207    }
208
209    /**
210     * set sub-component field character for this message
211     * @param subComponentDelimiter
212     */
213    public void setSubComponentDelimiter(char subComponentDelimiter) {
214      this.subComponentDelimiter = subComponentDelimiter;
215    }
216
217    /**
218     * reset to default HL7 values
219     *
220     */
221    public void reset () {
222      fieldDelimiter = DEFAULT_DELIMITER_FIELD;
223      componentDelimiter = DEFAULT_DELIMITER_COMPONENT;
224      subComponentDelimiter = DEFAULT_DELIMITER_SUBCOMPONENT;
225      repetitionDelimiter = DEFAULT_DELIMITER_REPETITION;
226      escapeCharacter = DEFAULT_CHARACTER_ESCAPE;
227    }
228    
229    /**
230     * check that the delimiters are valid
231     * 
232     * @throws FHIRException
233     */
234    public void check() throws FHIRException {
235        rule(componentDelimiter != fieldDelimiter, "Delimiter Error: \""+componentDelimiter+"\" is used for both CPComponent and CPField");
236        rule(subComponentDelimiter != fieldDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPField");
237        rule(subComponentDelimiter != componentDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPComponent");
238        rule(repetitionDelimiter != fieldDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPField");
239        rule(repetitionDelimiter != componentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPComponent");
240        rule(repetitionDelimiter != subComponentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPSubComponent");
241        rule(escapeCharacter != fieldDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPField");
242        rule(escapeCharacter != componentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPComponent");
243        rule(escapeCharacter != subComponentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPSubComponent");
244        rule(escapeCharacter != repetitionDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and Repetition");
245    }
246
247    /**
248     * check to see whether ch is a delimiter character (vertical bar parser support)
249     * @param ch
250     * @return
251     */
252    public boolean isDelimiter(char ch) {
253      return ch == escapeCharacter || ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter  || ch ==  componentDelimiter;
254    }
255
256    /** 
257     * check to see whether ch is a cell delimiter char (vertical bar parser support)
258     * @param ch
259     * @return
260     */
261    public boolean isCellDelimiter(char ch) {
262      return ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter  || ch ==  componentDelimiter;
263    }
264
265    /**
266     * get the escape for a character
267     * @param ch
268     * @return
269     */ 
270    public String getEscape(char ch) {
271      if (ch == escapeCharacter)
272        return escapeCharacter + "E" + escapeCharacter;
273      else if (ch == fieldDelimiter)
274        return escapeCharacter + "F" + escapeCharacter;
275      else if (ch == componentDelimiter)
276        return escapeCharacter + "S" + escapeCharacter;
277      else if (ch == subComponentDelimiter)
278        return escapeCharacter + "T" + escapeCharacter;
279      else if (ch == repetitionDelimiter)
280        return escapeCharacter + "R" + escapeCharacter;
281      else
282        return null;
283      }
284
285    /**
286     * build the MSH-2 content
287     * @return
288     */
289    public String forMSH2() {
290      return "" + componentDelimiter + repetitionDelimiter + escapeCharacter + subComponentDelimiter;
291    }
292
293    /** 
294     * check to see whether ch represents a delimiter escape
295     * @param ch
296     * @return
297     */
298    public boolean isDelimiterEscape(char ch) {
299      return ch == 'F' || ch == 'S'  || ch == 'E' || ch == 'T' || ch == 'R';
300    }
301
302    /** 
303     * get escape for ch in an escape
304     * @param ch
305     * @return
306     * @throws DefinitionException 
307     * @throws FHIRException
308     */
309    public char getDelimiterEscapeChar(char ch) throws DefinitionException {
310      if (ch == 'E')
311        return escapeCharacter;
312      else if (ch == 'F')
313        return fieldDelimiter;
314      else if (ch == 'S')
315        return componentDelimiter;
316      else if (ch == 'T')
317        return subComponentDelimiter;
318      else if (ch == 'R')
319        return repetitionDelimiter;
320      else
321        throw new DefinitionException("internal error in getDelimiterEscapeChar");
322    }
323  }
324  
325  public class VerticalBarParserReader {
326
327    
328    private BufferedInputStream stream;
329    private String charsetName; 
330    private InputStreamReader reader = null;
331    private boolean finished;
332    private char peeked;
333    private char lastValue;
334    private int offset;
335    private int lineNumber;
336
337    public VerticalBarParserReader(BufferedInputStream stream, String charsetName) throws FHIRException {
338      super();
339      setStream(stream);
340      setCharsetName(charsetName);
341      open();
342    }
343
344    public String getCharsetName() {
345      return charsetName;
346    }
347
348    public void setCharsetName(String charsetName) {
349      this.charsetName = charsetName;
350    }
351
352    public BufferedInputStream getStream() {
353      return stream;
354    }
355
356    public void setStream(BufferedInputStream stream) {
357      this.stream = stream;
358    }
359    
360    private void open() throws FHIRException {
361      try {
362        stream.mark(2048);
363        reader = new InputStreamReader(stream, charsetName);
364        offset = 0;
365        lineNumber = 0;
366        lastValue = ' ';
367        next();
368      } catch (Exception e) {
369        throw new FHIRException(e);
370      }
371    }
372
373    private void next() throws IOException, FHIRException {
374      finished = !reader.ready();
375      if (!finished) {
376        char[] temp = new char[1];
377        rule(reader.read(temp, 0, 1) == 1, "unable to read 1 character from the stream");
378        peeked = temp[0];
379      } 
380    }
381
382    public String read(int charCount) throws FHIRException {
383      String value = "";
384      for (int i = 0; i < charCount; i++) 
385        value = value + read();
386      return value;
387    }
388    
389    public void skipEOL () throws FHIRException {
390      while (!finished && (peek() == '\r' || peek() == '\n'))
391        read();
392    }
393    
394    public char read () throws FHIRException {
395      rule(!finished, "No more content to read");
396      char value = peek();
397      offset++;
398      if (value == '\r' || value == '\n') {
399        if (lastValue != '\r' || value != '\n')
400          lineNumber++;
401      }
402      lastValue = value;
403      try {
404        next();
405      } catch (Exception e) {
406        throw new FHIRException(e);
407      } 
408      return value;
409    }
410
411    public boolean isFinished () {
412      return finished;    
413    }
414    
415    public char peek() throws FHIRException {
416      rule(!finished, "Cannot peek");
417      return peeked;    
418    }
419
420    public void mark() {
421      stream.mark(2048);
422    }
423    
424    public void reset() throws FHIRException {
425      try {
426        stream.reset();
427      } catch (IOException e) {
428        throw new FHIRException(e);
429      }
430      open();
431    }
432
433    public boolean IsEOL() throws FHIRException {
434      return peek() == '\r' || peek() == '\n';
435    }
436
437    public int getLineNumber() {
438      return lineNumber;
439    }
440
441    public int getOffset() {
442      return offset;
443    }
444
445  }
446
447  public VerticalBarParser(IWorkerContext context) {
448    super(context);
449  }
450
451  private String charset = "ASCII";
452  private Delimiters delimiters = new Delimiters();
453  
454  @Override
455  public List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
456    StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/v2/StructureDefinition/Message");
457    Element message = new Element("Message", new Property(context, sd.getSnapshot().getElementFirstRep(), sd));
458    VerticalBarParserReader reader = new VerticalBarParserReader(new BufferedInputStream(stream), charset);
459    
460    preDecode(reader);
461    while (!reader.isFinished()) //  && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size()))
462      readSegment(message, reader);
463    List<NamedElement> res = new ArrayList<>();
464    res.add(new NamedElement(null, message));
465    return res;
466  }
467
468  private void preDecode(VerticalBarParserReader reader) throws FHIRException {
469    reader.skipEOL();
470    String temp = reader.read(3);
471    rule(temp.equals("MSH") || temp.equals("FHS"), "Found '" + temp + "' looking for 'MSH' or 'FHS'");
472    readDelimiters(reader);
473    // readVersion(message); - probably don't need to do that? 
474    // readCharacterSet();
475    reader.reset(); // ready to read message now
476  }
477
478  private void rule(boolean test, String msg) throws FHIRException {
479    if (!test)
480      throw new FHIRException(msg);    
481  }
482
483  private void readDelimiters(VerticalBarParserReader reader) throws FHIRException {
484    delimiters.setFieldDelimiter(reader.read());
485    if (!(reader.peek() == delimiters.getFieldDelimiter()))
486      delimiters.setComponentDelimiter(reader.read());
487    if (!(reader.peek() == delimiters.getFieldDelimiter()))
488      delimiters.setRepetitionDelimiter(reader.read());
489    if (!(reader.peek() == delimiters.getFieldDelimiter()))
490      delimiters.setEscapeCharacter(reader.read());
491    if (!(reader.peek() == delimiters.getFieldDelimiter()))
492      delimiters.setSubComponentDelimiter(reader.read());
493    delimiters.check();
494  }
495
496  private void readSegment(Element message, VerticalBarParserReader reader) throws FHIRException {
497    Element segment = new Element("segment", message.getProperty().getChild("segment"));
498    message.getChildren().add(segment);
499    Element segmentCode = new Element("code", segment.getProperty().getChild("code"));
500    segment.getChildren().add(segmentCode);
501    segmentCode.setValue(reader.read(3));
502    
503    int index = 0;
504    while (!reader.isFinished() && !reader.IsEOL()) {
505      index++;
506      readField(reader, segment, index);
507      if (!reader.isFinished() && !reader.IsEOL())
508        rule(reader.read() == delimiters.getFieldDelimiter(), "Expected to find field delimiter");
509    }
510    if (!reader.isFinished())
511      reader.skipEOL();    
512  }
513
514
515  
516  private void readField(VerticalBarParserReader reader, Element segment, int index) {
517    // TODO Auto-generated method stub
518    
519  }
520
521  @Override
522  public void compose(Element e, OutputStream destination, OutputStyle style, String base) {
523    // TODO Auto-generated method stub
524
525  }
526
527}