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