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