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