Coverage Report - ca.uhn.hl7v2.parser.OldPipeParser
 
Classes in this File Line Coverage Branch Coverage Complexity
OldPipeParser
44%
137/305
39%
70/176
4.613
OldPipeParser$1
100%
4/4
100%
2/2
4.613
OldPipeParser$2
100%
4/4
N/A
4.613
OldPipeParser$MessageStructure
100%
4/4
N/A
4.613
 
 1  
 /**
 2  
  * The contents of this file are subject to the Mozilla Public License Version 1.1
 3  
  * (the "License"); you may not use this file except in compliance with the License.
 4  
  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 5  
  * Software distributed under the License is distributed on an "AS IS" basis,
 6  
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
 7  
  * specific language governing rights and limitations under the License.
 8  
  *
 9  
  * The Original Code is "PipeParser.java".  Description:
 10  
  * "An implementation of Parser that supports traditionally encoded (i.e"
 11  
  *
 12  
  * The Initial Developer of the Original Code is University Health Network. Copyright (C)
 13  
  * 2001.  All Rights Reserved.
 14  
  *
 15  
  * Contributor(s): Kenneth Beaton.
 16  
  *
 17  
  * Alternatively, the contents of this file may be used under the terms of the
 18  
  * GNU General Public License (the  �GPL�), in which case the provisions of the GPL are
 19  
  * applicable instead of those above.  If you wish to allow use of your version of this
 20  
  * file only under the terms of the GPL and not to allow others to use your version
 21  
  * of this file under the MPL, indicate your decision by deleting  the provisions above
 22  
  * and replace  them with the notice and other provisions required by the GPL License.
 23  
  * If you do not delete the provisions above, a recipient may use your version of
 24  
  * this file under either the MPL or the GPL.
 25  
  *
 26  
  */
 27  
 
 28  
 package ca.uhn.hl7v2.parser;
 29  
 
 30  
 import java.util.ArrayList;
 31  
 import java.util.List;
 32  
 import java.util.StringTokenizer;
 33  
 
 34  
 import org.slf4j.Logger;
 35  
 import org.slf4j.LoggerFactory;
 36  
 
 37  
 import ca.uhn.hl7v2.ErrorCode;
 38  
 import ca.uhn.hl7v2.HL7Exception;
 39  
 import ca.uhn.hl7v2.Version;
 40  
 import ca.uhn.hl7v2.model.Group;
 41  
 import ca.uhn.hl7v2.model.Message;
 42  
 import ca.uhn.hl7v2.model.Primitive;
 43  
 import ca.uhn.hl7v2.model.Segment;
 44  
 import ca.uhn.hl7v2.model.Structure;
 45  
 import ca.uhn.hl7v2.model.Type;
 46  
 import ca.uhn.hl7v2.model.Varies;
 47  
 import ca.uhn.hl7v2.util.FilterIterator;
 48  
 import ca.uhn.hl7v2.util.MessageIterator;
 49  
 import ca.uhn.hl7v2.util.Terser;
 50  
 
 51  
 /**
 52  
  * This is a legacy implementation of the PipeParser and should not be used
 53  
  * for new projects.
 54  
  *
 55  
  * In version 1.0 of HAPI, a behaviour was corrected where unexpected segments
 56  
  * would be placed at the tail end of the first segment group encountered. Any
 57  
  * legacy code which still depends on previous behaviour can use this
 58  
  * implementation.
 59  
  *
 60  
  * @author Bryan Tripp (bryan_tripp@sourceforge.net)
 61  
  * @deprecated
 62  
  */
 63  20
 class OldPipeParser extends Parser {
 64  
     
 65  1
     private static final Logger log = LoggerFactory.getLogger(OldPipeParser.class);
 66  
     
 67  
     private final static String segDelim = "\r"; //see section 2.8 of spec
 68  
     
 69  
     /** Creates a new PipeParser */
 70  0
     public OldPipeParser() {
 71  0
     }
 72  
 
 73  
     /** 
 74  
      * Creates a new PipeParser 
 75  
      *  
 76  
      * @param theFactory custom factory to use for model class lookup 
 77  
      */
 78  
     public OldPipeParser(ModelClassFactory theFactory) {
 79  1
             super(theFactory);
 80  1
     }
 81  
     
 82  
     /**
 83  
      * Returns a String representing the encoding of the given message, if
 84  
      * the encoding is recognized.  For example if the given message appears
 85  
      * to be encoded using HL7 2.x XML rules then "XML" would be returned.
 86  
      * If the encoding is not recognized then null is returned.  That this
 87  
      * method returns a specific encoding does not guarantee that the
 88  
      * message is correctly encoded (e.g. well formed XML) - just that
 89  
      * it is not encoded using any other encoding than the one returned.
 90  
      */
 91  
     public String getEncoding(String message) {
 92  1
         String encoding = null;
 93  
         
 94  
         //quit if the string is too short
 95  1
         if (message.length() < 4)
 96  0
             return null;
 97  
         
 98  
         //see if it looks like this message is | encoded ...
 99  1
         boolean ok = true;
 100  
         
 101  
         //string should start with "MSH"
 102  1
         if (!message.startsWith("MSH"))
 103  0
             return null;
 104  
         
 105  
         //4th character of each segment should be field delimiter
 106  1
         char fourthChar = message.charAt(3);
 107  1
         StringTokenizer st = new StringTokenizer(message, String.valueOf(segDelim), false);
 108  7
         while (st.hasMoreTokens()) {
 109  6
             String x = st.nextToken();
 110  6
             if (x.length() > 0) {
 111  6
                 if (Character.isWhitespace(x.charAt(0)))
 112  0
                     x = stripLeadingWhitespace(x);
 113  6
                 if (x.length() >= 4 && x.charAt(3) != fourthChar)
 114  0
                     return null;
 115  
             }
 116  6
         }
 117  
         
 118  
         //should be at least 11 field delimiters (because MSH-12 is required)
 119  1
         int nextFieldDelimLoc = 0;
 120  12
         for (int i = 0; i < 11; i++) {
 121  11
             nextFieldDelimLoc = message.indexOf(fourthChar, nextFieldDelimLoc + 1);
 122  11
             if (nextFieldDelimLoc < 0)
 123  0
                 return null;
 124  
         }
 125  
         
 126  1
         if (ok)
 127  1
             encoding = "VB";
 128  
         
 129  1
         return encoding;
 130  
     }
 131  
     
 132  
     /**
 133  
      * @return the preferred encoding of this Parser
 134  
      */
 135  
     public String getDefaultEncoding() {
 136  0
         return "VB";
 137  
     }
 138  
     
 139  
     /**
 140  
      * Returns true if and only if the given encoding is supported
 141  
      * by this Parser.
 142  
      */
 143  
     public boolean supportsEncoding(String encoding) {
 144  1
         boolean supports = false;
 145  1
         if (encoding != null && encoding.equals("VB"))
 146  1
             supports = true;
 147  1
         return supports;
 148  
     }
 149  
     
 150  
     /**
 151  
      * @deprecated this method should not be public 
 152  
      * @param message
 153  
      * @return
 154  
      * @throws HL7Exception
 155  
      * @throws EncodingNotSupportedException
 156  
      */
 157  
     public String getMessageStructure(String message) throws HL7Exception, EncodingNotSupportedException {
 158  0
         return getStructure(message).messageStructure;
 159  
     }
 160  
     
 161  
     /**
 162  
      * @returns the message structure from MSH-9-3
 163  
      */
 164  
     private MessageStructure getStructure(String message) throws HL7Exception, EncodingNotSupportedException {
 165  1
         EncodingCharacters ec = getEncodingChars(message);
 166  1
         String messageStructure = null;
 167  1
         boolean explicityDefined = true;
 168  
         String wholeFieldNine;
 169  
         try {
 170  1
             String[] fields = split(message.substring(0, Math.max(message.indexOf(segDelim), message.length())),
 171  
                 String.valueOf(ec.getFieldSeparator()));
 172  1
             wholeFieldNine = fields[8];
 173  
             
 174  
             //message structure is component 3 but we'll accept a composite of 1 and 2 if there is no component 3 ...
 175  
             //      if component 1 is ACK, then the structure is ACK regardless of component 2
 176  1
             String[] comps = split(wholeFieldNine, String.valueOf(ec.getComponentSeparator()));
 177  1
             if (comps.length >= 3) {
 178  0
                 messageStructure = comps[2];
 179  1
             } else if (comps.length > 0 && comps[0] != null && comps[0].equals("ACK")) {
 180  0
                 messageStructure = "ACK";
 181  1
             } else if (comps.length == 2) {
 182  1
                 explicityDefined = false;
 183  1
                 messageStructure = comps[0] + "_" + comps[1];
 184  
             }
 185  
             /*else if (comps.length == 1 && comps[0] != null && comps[0].equals("ACK")) {
 186  
                 messageStructure = "ACK"; //it's common for people to only populate component 1 in an ACK msg
 187  
             }*/
 188  
             else {
 189  0
                 StringBuffer buf = new StringBuffer("Can't determine message structure from MSH-9: ");
 190  0
                 buf.append(wholeFieldNine);
 191  0
                 if (comps.length < 3) {
 192  0
                     buf.append(" HINT: there are only ");
 193  0
                     buf.append(comps.length);
 194  0
                     buf.append(" of 3 components present");
 195  
                 }
 196  0
                 throw new HL7Exception(buf.toString(), ErrorCode.UNSUPPORTED_MESSAGE_TYPE);
 197  
             }            
 198  
         }
 199  0
         catch (IndexOutOfBoundsException e) {
 200  0
             throw new HL7Exception(
 201  
             "Can't find message structure (MSH-9-3): " + e.getMessage(),
 202  
             ErrorCode.UNSUPPORTED_MESSAGE_TYPE);
 203  1
         }
 204  
         
 205  1
         return new MessageStructure(messageStructure, explicityDefined);
 206  
     }
 207  
     
 208  
     /**
 209  
      * Returns object that contains the field separator and encoding characters
 210  
      * for this message.
 211  
      */
 212  
     private static EncodingCharacters getEncodingChars(String message) {
 213  7
         return new EncodingCharacters(message.charAt(3), message.substring(4, 8));
 214  
     }
 215  
     
 216  
     /**
 217  
      * Parses a message string and returns the corresponding Message
 218  
      * object.  Unexpected segments added at the end of their group.  
 219  
      *
 220  
      * @throws HL7Exception if the message is not correctly formatted.
 221  
      * @throws EncodingNotSupportedException if the message encoded
 222  
      *      is not supported by this parser.
 223  
      */
 224  
     protected Message doParse(String message, String version) throws HL7Exception, EncodingNotSupportedException {
 225  
         
 226  
         //try to instantiate a message object of the right class
 227  1
         MessageStructure structure = getStructure(message);
 228  1
         Message m = instantiateMessage(structure.messageStructure, version, structure.explicitlyDefined);
 229  
 
 230  1
         parse(m, message);
 231  
 
 232  1
         return m;
 233  
     }
 234  
     
 235  
     /**
 236  
      * Parses a segment string and populates the given Segment object.  Unexpected fields are
 237  
      * added as Varies' at the end of the segment.  
 238  
      *
 239  
      * @throws HL7Exception if the given string does not contain the
 240  
      *      given segment or if the string is not encoded properly
 241  
      */
 242  
     public void parse(Segment destination, String segment, EncodingCharacters encodingChars) throws HL7Exception {
 243  6
         int fieldOffset = 0;
 244  6
         if (isDelimDefSegment(destination.getName())) {
 245  1
             fieldOffset = 1;
 246  
             //set field 1 to fourth character of string
 247  1
             Terser.set(destination, 1, 0, 1, 1, String.valueOf(encodingChars.getFieldSeparator()));
 248  
         }
 249  
         
 250  6
         String[] fields = split(segment, String.valueOf(encodingChars.getFieldSeparator()));
 251  
         //destination.setName(fields[0]);
 252  102
         for (int i = 1; i < fields.length; i++) {
 253  96
             String[] reps = split(fields[i], String.valueOf(encodingChars.getRepetitionSeparator()));
 254  96
             log.debug("{} reps delimited by: {}", reps.length, encodingChars.getRepetitionSeparator());                
 255  
             
 256  
             //MSH-2 will get split incorrectly so we have to fudge it ...
 257  96
             boolean isMSH2 = isDelimDefSegment(destination.getName()) && i+fieldOffset == 2;
 258  96
             if (isMSH2) {  
 259  1
                 reps = new String[1];
 260  1
                 reps[0] = fields[i];
 261  
             }
 262  
             
 263  144
             for (int j = 0; j < reps.length; j++) {
 264  
                 try {
 265  48
                     StringBuffer statusMessage = new StringBuffer("Parsing field ");
 266  48
                     statusMessage.append(i+fieldOffset);
 267  48
                     statusMessage.append(" repetition ");
 268  48
                     statusMessage.append(j);
 269  48
                     log.debug(statusMessage.toString());
 270  
                     //parse(destination.getField(i + fieldOffset, j), reps[j], encodingChars, false);
 271  
 
 272  48
                     Type field = destination.getField(i + fieldOffset, j);
 273  48
                     if (isMSH2) {
 274  1
                         Terser.getPrimitive(field, 1, 1).setValue(reps[j]);
 275  
                     } else {
 276  47
                         parse(field, reps[j], encodingChars);
 277  
                     }
 278  
                 }
 279  0
                 catch (HL7Exception e) {
 280  
                     //set the field location and throw again ...
 281  0
                     e.setFieldPosition(i);
 282  0
                     e.setSegmentRepetition(MessageIterator.getIndex(destination.getParent(), destination).rep);
 283  0
                     e.setSegmentName(destination.getName());
 284  0
                     throw e;
 285  48
                 }
 286  
             }
 287  
         }
 288  
         
 289  
         //set data type of OBX-5
 290  6
         if (destination.getClass().getName().indexOf("OBX") >= 0) {
 291  0
             Varies.fixOBX5(destination, getFactory(), getHapiContext().getParserConfiguration());
 292  
         }
 293  
         
 294  6
     }
 295  
     
 296  
     /** 
 297  
      * @return true if the segment is MSH, FHS, or BHS.  These need special treatment 
 298  
      *  because they define delimiters.
 299  
      * @param theSegmentName
 300  
      */
 301  
     private static boolean isDelimDefSegment(String theSegmentName) {
 302  102
         boolean is = false;
 303  102
         if (theSegmentName.equals("MSH") 
 304  
             || theSegmentName.equals("FHS") 
 305  
             || theSegmentName.equals("BHS")) 
 306  
         {
 307  17
             is = true;
 308  
         }
 309  102
         return is;
 310  
     }
 311  
     
 312  
     /**
 313  
      * Fills a field with values from an unparsed string representing the field.  
 314  
      * @param destinationField the field Type
 315  
      * @param data the field string (including all components and subcomponents; not including field delimiters)
 316  
      * @param encodingCharacters the encoding characters used in the message
 317  
      */
 318  
     public void parse(Type destinationField, String data, EncodingCharacters encodingCharacters) throws HL7Exception {
 319  47
         String[] components = split(data, String.valueOf(encodingCharacters.getComponentSeparator()));
 320  116
         for (int i = 0; i < components.length; i++) {
 321  69
             String[] subcomponents = split(components[i], String.valueOf(encodingCharacters.getSubcomponentSeparator()));
 322  135
             for (int j = 0; j < subcomponents.length; j++) {
 323  66
                 String val = subcomponents[j];
 324  66
                 if (val != null) {
 325  66
                     val = Escape.unescape(val, encodingCharacters);
 326  
                 }
 327  66
                 Terser.getPrimitive(destinationField, i+1, j+1).setValue(val);                
 328  
             }
 329  
         }
 330  47
     }
 331  
     
 332  
     /**
 333  
      * Splits the given composite string into an array of components using the given
 334  
      * delimiter.
 335  
      */
 336  
     public static String[] split(String composite, String delim) {
 337  223
         List<String> components = new ArrayList<String>();
 338  
         
 339  
         //defend against evil nulls
 340  223
         if (composite == null)
 341  53
             composite = "";
 342  223
         if (delim == null)
 343  0
             delim = "";
 344  
         
 345  223
         StringTokenizer tok = new StringTokenizer(composite, delim, true);
 346  223
         boolean previousTokenWasDelim = true;
 347  782
         while (tok.hasMoreTokens()) {
 348  559
             String thisTok = tok.nextToken();
 349  559
             if (thisTok.equals(delim)) {
 350  251
                 if (previousTokenWasDelim)
 351  105
                     components.add(null);
 352  251
                 previousTokenWasDelim = true;
 353  
             }
 354  
             else {
 355  308
                 components.add(thisTok);
 356  308
                 previousTokenWasDelim = false;
 357  
             }
 358  559
         }
 359  
         
 360  223
         return components.toArray(new String[components.size()]);
 361  
     }
 362  
     
 363  
     /**
 364  
      * Encodes the given Type, using the given encoding characters. 
 365  
      * It is assumed that the Type represents a complete field rather than a component.
 366  
      */
 367  
     public static String encode(Type source, EncodingCharacters encodingChars) {
 368  0
         StringBuffer field = new StringBuffer();
 369  0
         for (int i = 1; i <= Terser.numComponents(source); i++) {
 370  0
             StringBuffer comp = new StringBuffer();
 371  0
             for (int j = 1; j <= Terser.numSubComponents(source, i); j++) {
 372  0
                 Primitive p = Terser.getPrimitive(source, i, j);
 373  0
                 comp.append(encodePrimitive(p, encodingChars));
 374  0
                 comp.append(encodingChars.getSubcomponentSeparator());
 375  
             }
 376  0
             field.append(stripExtraDelimiters(comp.toString(), encodingChars.getSubcomponentSeparator()));
 377  0
             field.append(encodingChars.getComponentSeparator());
 378  
         }
 379  0
         return stripExtraDelimiters(field.toString(), encodingChars.getComponentSeparator());
 380  
         //return encode(source, encodingChars, false);
 381  
     }
 382  
     
 383  
     private static String encodePrimitive(Primitive p, EncodingCharacters encodingChars) {
 384  0
         String val = ((Primitive) p).getValue();
 385  0
         if (val == null) {
 386  0
             val = "";
 387  
         } else {
 388  0
             val = Escape.escape(val, encodingChars);
 389  
         }
 390  0
         return val;
 391  
     }
 392  
     
 393  
     /**
 394  
      * Removes unecessary delimiters from the end of a field or segment.
 395  
      * This seems to be more convenient than checking to see if they are needed
 396  
      * while we are building the encoded string.
 397  
      */
 398  
     private static String stripExtraDelimiters(String in, char delim) {
 399  0
         char[] chars = in.toCharArray();
 400  
         
 401  
         //search from back end for first occurance of non-delimiter ...
 402  0
         int c = chars.length - 1;
 403  0
         boolean found = false;
 404  0
         while (c >= 0 && !found) {
 405  0
             if (chars[c--] != delim)
 406  0
                 found = true;
 407  
         }
 408  
         
 409  0
         String ret = "";
 410  0
         if (found)
 411  0
             ret = String.valueOf(chars, 0, c + 2);
 412  0
         return ret;
 413  
     }
 414  
     
 415  
     /**
 416  
      * Formats a Message object into an HL7 message string using the given
 417  
      * encoding.
 418  
      * @throws HL7Exception if the data fields in the message do not permit encoding
 419  
      *      (e.g. required fields are null)
 420  
      * @throws EncodingNotSupportedException if the requested encoding is not
 421  
      *      supported by this parser.
 422  
      */
 423  
     protected String doEncode(Message source, String encoding) throws HL7Exception, EncodingNotSupportedException {
 424  0
         if (!this.supportsEncoding(encoding))
 425  0
             throw new EncodingNotSupportedException("This parser does not support the " + encoding + " encoding");
 426  
         
 427  0
         return encode(source);
 428  
     }
 429  
     
 430  
     /**
 431  
      * Formats a Message object into an HL7 message string using this parser's
 432  
      * default encoding ("VB").
 433  
      * @throws HL7Exception if the data fields in the message do not permit encoding
 434  
      *      (e.g. required fields are null)
 435  
      */
 436  
     protected String doEncode(Message source) throws HL7Exception {
 437  
         //get encoding characters ...
 438  0
         Segment msh = (Segment) source.get("MSH");
 439  0
         String fieldSepString = Terser.get(msh, 1, 0, 1, 1);
 440  
         
 441  0
         if (fieldSepString == null) 
 442  0
             throw new HL7Exception("Can't encode message: MSH-1 (field separator) is missing");
 443  
         
 444  0
         char fieldSep = '|';
 445  0
         if (fieldSepString != null && fieldSepString.length() > 0)
 446  0
             fieldSep = fieldSepString.charAt(0);
 447  
         
 448  0
         String encCharString = Terser.get(msh, 2, 0, 1, 1);
 449  
         
 450  0
         if (encCharString == null) 
 451  0
             throw new HL7Exception("Can't encode message: MSH-2 (encoding characters) is missing");
 452  
                 
 453  0
         if (encCharString.length() != 4)
 454  0
             throw new HL7Exception(
 455  
             "Encoding characters '" + encCharString + "' invalid -- must be 4 characters",
 456  
             ErrorCode.DATA_TYPE_ERROR);
 457  0
         EncodingCharacters en = new EncodingCharacters(fieldSep, encCharString);
 458  
         
 459  
         //pass down to group encoding method which will operate recursively on children ...
 460  0
         return encode((Group) source, en);
 461  
     }
 462  
     
 463  
     /**
 464  
      * Returns given group serialized as a pipe-encoded string - this method is called
 465  
      * by encode(Message source, String encoding).
 466  
      */
 467  
     public static String encode(Group source, EncodingCharacters encodingChars) throws HL7Exception {
 468  0
         StringBuffer result = new StringBuffer();
 469  
         
 470  0
         String[] names = source.getNames();
 471  0
         for (int i = 0; i < names.length; i++) {
 472  0
             Structure[] reps = source.getAll(names[i]);
 473  0
             for (int rep = 0; rep < reps.length; rep++) {
 474  0
                 if (reps[rep] instanceof Group) {
 475  0
                     result.append(encode((Group) reps[rep], encodingChars));
 476  
                 }
 477  
                 else {
 478  0
                     String segString = encode((Segment) reps[rep], encodingChars);
 479  0
                     if (segString.length() >= 4) {
 480  0
                         result.append(segString);
 481  0
                         result.append('\r');
 482  
                     }
 483  
                 }
 484  
             }
 485  
         }
 486  0
         return result.toString();
 487  
     }
 488  
     
 489  
     public static String encode(Segment source, EncodingCharacters encodingChars) {
 490  0
         StringBuffer result = new StringBuffer();
 491  0
         result.append(source.getName());
 492  0
         result.append(encodingChars.getFieldSeparator());
 493  
         
 494  
         //start at field 2 for MSH segment because field 1 is the field delimiter
 495  0
         int startAt = 1;
 496  0
         if (isDelimDefSegment(source.getName()))
 497  0
             startAt = 2;
 498  
         
 499  
         //loop through fields; for every field delimit any repetitions and add field delimiter after ...
 500  0
         int numFields = source.numFields();
 501  0
         for (int i = startAt; i <= numFields; i++) {
 502  
             try {
 503  0
                 Type[] reps = source.getField(i);
 504  0
                 for (int j = 0; j < reps.length; j++) {
 505  0
                     String fieldText = encode(reps[j], encodingChars);
 506  
                     //if this is MSH-2, then it shouldn't be escaped, so unescape it again
 507  0
                     if (isDelimDefSegment(source.getName()) && i == 2)
 508  0
                         fieldText = Escape.unescape(fieldText, encodingChars);
 509  0
                     result.append(fieldText);
 510  0
                     if (j < reps.length - 1)
 511  0
                         result.append(encodingChars.getRepetitionSeparator());
 512  
                 }
 513  
             }
 514  0
             catch (HL7Exception e) {
 515  0
                 log.error("Error while encoding segment: ", e);
 516  0
             }
 517  0
             result.append(encodingChars.getFieldSeparator());
 518  
         }
 519  
         
 520  
         //strip trailing delimiters ...
 521  0
         return stripExtraDelimiters(result.toString(), encodingChars.getFieldSeparator());
 522  
     }
 523  
     
 524  
     /**
 525  
      * Removes leading whitespace from the given string.  This method was created to deal with frequent
 526  
      * problems parsing messages that have been hand-written in windows.  The intuitive way to delimit
 527  
      * segments is to hit <ENTER> at the end of each segment, but this creates both a carriage return
 528  
      * and a line feed, so to the parser, the first character of the next segment is the line feed.
 529  
      */
 530  
     public static String stripLeadingWhitespace(String in) {
 531  0
         StringBuffer out = new StringBuffer();
 532  0
         char[] chars = in.toCharArray();
 533  0
         int c = 0;
 534  0
         while (c < chars.length) {
 535  0
             if (!Character.isWhitespace(chars[c]))
 536  0
                 break;
 537  0
             c++;
 538  
         }
 539  0
         for (int i = c; i < chars.length; i++) {
 540  0
             out.append(chars[i]);
 541  
         }
 542  0
         return out.toString();
 543  
     }
 544  
     
 545  
     /**
 546  
      * <p>Returns a minimal amount of data from a message string, including only the
 547  
      * data needed to send a response to the remote system.  This includes the
 548  
      * following fields:
 549  
      * <ul><li>field separator</li>
 550  
      * <li>encoding characters</li>
 551  
      * <li>processing ID</li>
 552  
      * <li>message control ID</li></ul>
 553  
      * This method is intended for use when there is an error parsing a message,
 554  
      * (so the Message object is unavailable) but an error message must be sent
 555  
      * back to the remote system including some of the information in the inbound
 556  
      * message.  This method parses only that required information, hopefully
 557  
      * avoiding the condition that caused the original error.  The other
 558  
      * fields in the returned MSH segment are empty.</p>
 559  
      */
 560  
     public Segment getCriticalResponseData(String message) throws HL7Exception {
 561  
         //try to get MSH segment
 562  0
         int locStartMSH = message.indexOf("MSH");
 563  0
         if (locStartMSH < 0)
 564  0
             throw new HL7Exception(
 565  
             "Couldn't find MSH segment in message: " + message,
 566  
             ErrorCode.SEGMENT_SEQUENCE_ERROR);
 567  0
         int locEndMSH = message.indexOf('\r', locStartMSH + 1);
 568  0
         if (locEndMSH < 0)
 569  0
             locEndMSH = message.length();
 570  0
         String mshString = message.substring(locStartMSH, locEndMSH);
 571  
         
 572  
         //find out what the field separator is
 573  0
         char fieldSep = mshString.charAt(3);
 574  
         
 575  
         //get field array
 576  0
         String[] fields = split(mshString, String.valueOf(fieldSep));
 577  
         
 578  0
         Segment msh = null;
 579  
         try {
 580  
             //parse required fields
 581  0
             String encChars = fields[1];
 582  0
             char compSep = encChars.charAt(0);
 583  0
             String messControlID = fields[9];
 584  0
             String[] procIDComps = split(fields[10], String.valueOf(compSep));
 585  
             
 586  
             //fill MSH segment
 587  0
             String version = Version.lowestAvailableVersion().getVersion(); //default
 588  
             try {
 589  0
                 version = this.getVersion(message);
 590  
             }
 591  0
             catch (Exception e) { /* use the default */
 592  0
             }
 593  
             
 594  0
             msh = Parser.makeControlMSH(version, getFactory());
 595  0
             Terser.set(msh, 1, 0, 1, 1, String.valueOf(fieldSep));
 596  0
             Terser.set(msh, 2, 0, 1, 1, encChars);
 597  0
             Terser.set(msh, 10, 0, 1, 1, messControlID);
 598  0
             Terser.set(msh, 11, 0, 1, 1, procIDComps[0]);
 599  0
             Terser.set(msh, 12, 0, 1, 1, version);
 600  
             
 601  
             }
 602  0
         catch (Exception e) {
 603  0
             throw new HL7Exception(
 604  
             "Can't parse critical fields from MSH segment ("
 605  
             + e.getClass().getName()
 606  
             + ": "
 607  
             + e.getMessage()
 608  
             + "): "
 609  
             + mshString,
 610  
             ErrorCode.REQUIRED_FIELD_MISSING, e);
 611  0
         }
 612  
         
 613  0
         return msh;
 614  
     }
 615  
     
 616  
     /**
 617  
      * For response messages, returns the value of MSA-2 (the message ID of the message
 618  
      * sent by the sending system).  This value may be needed prior to main message parsing,
 619  
      * so that (particularly in a multi-threaded scenario) the message can be routed to
 620  
      * the thread that sent the request.  We need this information first so that any
 621  
      * parse exceptions are thrown to the correct thread.
 622  
      * Returns null if MSA-2 can not be found (e.g. if the message is not a
 623  
      * response message).
 624  
      */
 625  
     public String getAckID(String message) {
 626  0
         String ackID = null;
 627  0
         int startMSA = message.indexOf("\rMSA");
 628  0
         if (startMSA >= 0) {
 629  0
             int startFieldOne = startMSA + 5;
 630  0
             char fieldDelim = message.charAt(startFieldOne - 1);
 631  0
             int start = message.indexOf(fieldDelim, startFieldOne) + 1;
 632  0
             int end = message.indexOf(fieldDelim, start);
 633  0
             int segEnd = message.indexOf(String.valueOf(segDelim), start);
 634  0
             if (segEnd > start && segEnd < end)
 635  0
                 end = segEnd;
 636  
             
 637  
             //if there is no field delim after MSH-2, need to go to end of message, but not including end seg delim if it exists
 638  0
             if (end < 0) {
 639  0
                 if (message.charAt(message.length() - 1) == '\r') {
 640  0
                     end = message.length() - 1;
 641  
                 }
 642  
                 else {
 643  0
                     end = message.length();
 644  
                 }
 645  
             }
 646  0
             if (start > 0 && end > start) {
 647  0
                 ackID = message.substring(start, end);
 648  
             }
 649  
         }
 650  0
         log.debug("ACK ID: {}", ackID);
 651  0
         return ackID;
 652  
     }
 653  
     
 654  
     /**
 655  
      * Returns the version ID (MSH-12) from the given message, without fully parsing the message.
 656  
      * The version is needed prior to parsing in order to determine the message class
 657  
      * into which the text of the message should be parsed.
 658  
      * @throws HL7Exception if the version field can not be found.
 659  
      */
 660  
     public String getVersion(String message) throws HL7Exception {
 661  1
         int startMSH = message.indexOf("MSH");
 662  1
         int endMSH = message.indexOf(OldPipeParser.segDelim, startMSH);
 663  1
         if (endMSH < 0)
 664  0
             endMSH = message.length();
 665  1
         String msh = message.substring(startMSH, endMSH);
 666  1
         String fieldSep = null;
 667  1
         if (msh.length() > 3) {
 668  1
             fieldSep = String.valueOf(msh.charAt(3));
 669  
         }
 670  
         else {
 671  0
             throw new HL7Exception("Can't find field separator in MSH: " + msh, ErrorCode.UNSUPPORTED_VERSION_ID);
 672  
         }
 673  
         
 674  1
         String[] fields = split(msh, fieldSep);
 675  
         
 676  1
         String compSep = null;
 677  1
         if (fields.length >= 2 && fields[1] != null && fields[1].length() == 4) {
 678  1
             compSep = String.valueOf(fields[1].charAt(0)); //get component separator as 1st encoding char
 679  
         } 
 680  
         else {
 681  0
             throw new HL7Exception("Invalid or incomplete encoding characters - MSH-2 is " + fields[1],  
 682  
                     ErrorCode.REQUIRED_FIELD_MISSING);
 683  
         }
 684  
         
 685  1
         String version = null;
 686  1
         if (fields.length >= 12) {
 687  1
                 String[] comp = split(fields[11], compSep);
 688  1
                 if (comp.length >= 1) {
 689  1
                         version = comp[0];
 690  
                 } else {
 691  0
                         throw new HL7Exception("Can't find version ID - MSH.12 is " + fields[11],
 692  
                                         ErrorCode.REQUIRED_FIELD_MISSING);
 693  
                 }
 694  1
         }
 695  
         else {
 696  0
             throw new HL7Exception(
 697  
                             "Can't find version ID - MSH has only " + fields.length + " fields.",
 698  
                             ErrorCode.REQUIRED_FIELD_MISSING);
 699  
         }
 700  1
         return version;
 701  
     }
 702  
 
 703  
     /**
 704  
      * {@inheritDoc }
 705  
      */
 706  
     public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception {
 707  0
         return encode(structure, encodingCharacters);
 708  
     }
 709  
 
 710  
     /**
 711  
      * {@inheritDoc }
 712  
      */
 713  
     public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception {
 714  0
         return encode(type, encodingCharacters);
 715  
     }
 716  
 
 717  
     /**
 718  
      * Throws unsupported operation exception
 719  
      *
 720  
      * @throws Unsupported operation exception
 721  
      */
 722  
     @Override
 723  
         protected Message doParseForSpecificPackage(String theMessage, String theVersion, String thePackageName) throws HL7Exception, EncodingNotSupportedException {
 724  0
         throw new UnsupportedOperationException("Not supported yet.");
 725  
         }
 726  
     
 727  
     public void parse(Message message, String string) throws HL7Exception {
 728  
         //MessagePointer ptr = new MessagePointer(this, m, getEncodingChars(message));
 729  1
         MessageIterator messageIter = new MessageIterator(message, "MSH", true);
 730  1
         FilterIterator.Predicate<Structure> segmentsOnly = new FilterIterator.Predicate<Structure>() {
 731  
             public boolean evaluate(Structure obj) {
 732  26
                 if (Segment.class.isAssignableFrom(obj.getClass())) {
 733  20
                     return true;
 734  
                 } else {
 735  6
                     return false;
 736  
                 }
 737  
             }
 738  
         };
 739  1
         FilterIterator<Structure> segmentIter = new FilterIterator<Structure>(messageIter, segmentsOnly);
 740  
 
 741  1
         String[] segments = split(string, segDelim);
 742  
 
 743  1
         char delim = '|';
 744  7
         for (int i = 0; i < segments.length; i++) {
 745  
 
 746  
             //get rid of any leading whitespace characters ...
 747  6
             if (segments[i] != null && segments[i].length() > 0 && Character.isWhitespace(segments[i].charAt(0)))
 748  0
                 segments[i] = stripLeadingWhitespace(segments[i]);
 749  
 
 750  
             //sometimes people put extra segment delimiters at end of msg ...
 751  6
             if (segments[i] != null && segments[i].length() >= 3) {
 752  
                 final String name;
 753  6
                 if (i == 0) {
 754  1
                     name = segments[i].substring(0, 3);
 755  1
                     delim = segments[i].charAt(3);
 756  
                 } else {
 757  5
                     if (segments[i].indexOf(delim) >= 0 ) {
 758  5
                         name = segments[i].substring(0, segments[i].indexOf(delim));
 759  
                       } else {
 760  0
                         name = segments[i];
 761  
                       }
 762  
                  }
 763  
 
 764  6
                 log.debug("Parsing segment {}", name);
 765  
 
 766  6
                 messageIter.setDirection(name);
 767  6
                 FilterIterator.Predicate<Structure> byDirection = new FilterIterator.Predicate<Structure>() {
 768  
                     public boolean evaluate(Structure obj) {
 769  20
                         Structure s = (Structure) obj;
 770  20
                         log.debug("PipeParser iterating message in direction {} at {} ", name, s.getName());
 771  20
                         return s.getName().matches(name + "\\d*");
 772  
                     }
 773  
                 };
 774  6
                 FilterIterator<Structure> dirIter = new FilterIterator<Structure>(segmentIter, byDirection);
 775  6
                 if (dirIter.hasNext()) {
 776  6
                     parse((Segment) dirIter.next(), segments[i], getEncodingChars(string));
 777  
                 }
 778  
             }
 779  
         }
 780  1
     }
 781  
 
 782  
     
 783  
     /**
 784  
      * A struct for holding a message class string and a boolean indicating whether it 
 785  
      * was defined explicitly.  
 786  
      */
 787  
     private static class MessageStructure {
 788  
         public String messageStructure;
 789  
         public boolean explicitlyDefined;
 790  
         
 791  1
         public MessageStructure(String theMessageStructure, boolean isExplicitlyDefined) {
 792  1
             messageStructure = theMessageStructure;
 793  1
             explicitlyDefined = isExplicitlyDefined;
 794  1
         }
 795  
     }
 796  
     
 797  
 }