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