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