Coverage Report - ca.uhn.hl7v2.parser.PipeParser
 
Classes in this File Line Coverage Branch Coverage Complexity
PipeParser
87%
398/455
82%
252/306
5.156
PipeParser$1
N/A
N/A
5.156
PipeParser$Holder
100%
4/4
N/A
5.156
PipeParser$MessageStructure
100%
4/4
N/A
5.156
 
 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.Arrays;
 32  
 import java.util.HashMap;
 33  
 import java.util.List;
 34  
 import java.util.Map;
 35  
 import java.util.Set;
 36  
 import java.util.StringTokenizer;
 37  
 
 38  
 import ca.uhn.hl7v2.validation.ValidationContext;
 39  
 import org.slf4j.Logger;
 40  
 import org.slf4j.LoggerFactory;
 41  
 
 42  
 import ca.uhn.hl7v2.DefaultHapiContext;
 43  
 import ca.uhn.hl7v2.ErrorCode;
 44  
 import ca.uhn.hl7v2.HL7Exception;
 45  
 import ca.uhn.hl7v2.HapiContext;
 46  
 import ca.uhn.hl7v2.Version;
 47  
 import ca.uhn.hl7v2.model.AbstractSuperMessage;
 48  
 import ca.uhn.hl7v2.model.DoNotCacheStructure;
 49  
 import ca.uhn.hl7v2.model.Group;
 50  
 import ca.uhn.hl7v2.model.Message;
 51  
 import ca.uhn.hl7v2.model.Primitive;
 52  
 import ca.uhn.hl7v2.model.Segment;
 53  
 import ca.uhn.hl7v2.model.Structure;
 54  
 import ca.uhn.hl7v2.model.SuperStructure;
 55  
 import ca.uhn.hl7v2.model.Type;
 56  
 import ca.uhn.hl7v2.model.Varies;
 57  
 import ca.uhn.hl7v2.util.ReflectionUtil;
 58  
 import ca.uhn.hl7v2.util.Terser;
 59  
 import ca.uhn.hl7v2.validation.impl.NoValidation;
 60  
 import ca.uhn.hl7v2.validation.impl.ValidationContextFactory;
 61  
 
 62  
 /**
 63  
  * An implementation of Parser that supports traditionally encoded (ie delimited
 64  
  * with characters like |, ^, and ~) HL7 messages. Unexpected segments and
 65  
  * fields are parsed into generic elements that are added to the message.
 66  
  * 
 67  
  * @see ParserConfiguration for configuration options which may affect parser encoding and decoding behaviour
 68  
  * @author Bryan Tripp (bryan_tripp@sourceforge.net)
 69  
  */
 70  
 public class PipeParser extends Parser {
 71  
 
 72  5
         private static final Logger log = LoggerFactory.getLogger(PipeParser.class);
 73  
 
 74  
         /**
 75  
          * The HL7 ER7 segment delimiter (see section 2.8 of spec)
 76  
          */
 77  
         final static String SEGMENT_DELIMITER = "\r";
 78  
 
 79  5275
         private final HashMap<Class<? extends Message>, HashMap<String, StructureDefinition>> myStructureDefinitions = new HashMap<Class<? extends Message>, HashMap<String, StructureDefinition>>();
 80  
 
 81  
         /**
 82  
          * System property key. If value is "true", legacy mode will default to true
 83  
          * 
 84  
          * @see #isLegacyMode()
 85  
          * @deprecated This will be removed in HAPI 3.0
 86  
          */
 87  
         public static final String DEFAULT_LEGACY_MODE_PROPERTY = "ca.uhn.hl7v2.parser.PipeParser.default_legacy_mode";
 88  
 
 89  5275
         private Boolean myLegacyMode = null;
 90  
 
 91  
         public PipeParser() {
 92  4580
                 super();
 93  4580
         }
 94  
 
 95  
         /**
 96  
          * @param context
 97  
          *            the context containing all configuration items to be used
 98  
          */
 99  
         public PipeParser(HapiContext context) {
 100  675
                 super(context);
 101  675
         }
 102  
 
 103  
         /**
 104  
          * Creates a new PipeParser
 105  
          * 
 106  
          * @param theFactory
 107  
          *            custom factory to use for model class lookup
 108  
          */
 109  
         public PipeParser(ModelClassFactory theFactory) {
 110  20
                 super(theFactory);
 111  20
         }
 112  
 
 113  
     @Override
 114  
     public void setValidationContext(ValidationContext context) {
 115  0
         super.setValidationContext(context);
 116  0
     }
 117  
 
 118  
     /**
 119  
          * Returns a String representing the encoding of the given message, if the
 120  
          * encoding is recognized. For example if the given message appears to be
 121  
          * encoded using HL7 2.x XML rules then "XML" would be returned. If the
 122  
          * encoding is not recognized then null is returned. That this method
 123  
          * returns a specific encoding does not guarantee that the message is
 124  
          * correctly encoded (e.g. well formed XML) - just that it is not encoded
 125  
          * using any other encoding than the one returned.
 126  
          */
 127  
         public String getEncoding(String message) {
 128  6654
                 return EncodingDetector.isEr7Encoded(message) ? getDefaultEncoding() : null;
 129  
         }
 130  
 
 131  
         /**
 132  
          * @return the preferred encoding of this Parser
 133  
          */
 134  
         public String getDefaultEncoding() {
 135  11569
                 return "VB";
 136  
         }
 137  
 
 138  
         /**
 139  
          * @deprecated this method should not be public
 140  
          * @param message HL7 message
 141  
          * @return message structure
 142  
          * @throws HL7Exception
 143  
          */
 144  
         public String getMessageStructure(String message) throws HL7Exception {
 145  0
                 return getStructure(message).messageStructure;
 146  
         }
 147  
 
 148  
         /**
 149  
          * @return the message structure from MSH-9-3
 150  
          */
 151  
         private MessageStructure getStructure(String message) throws HL7Exception {
 152  3046
                 EncodingCharacters ec = getEncodingChars(message);
 153  
                 String messageStructure;
 154  3046
                 boolean explicityDefined = true;
 155  
                 String wholeFieldNine;
 156  
                 try {
 157  3046
                         String[] fields = split(message.substring(0, Math.max(message.indexOf(SEGMENT_DELIMITER), message.length())), String.valueOf(ec.getFieldSeparator()));
 158  3046
                         wholeFieldNine = fields[8];
 159  
 
 160  
                         // message structure is component 3 but we'll accept a composite of
 161  
                         // 1 and 2 if there is no component 3 ...
 162  
                         // if component 1 is ACK, then the structure is ACK regardless of
 163  
                         // component 2
 164  3046
                         String[] comps = split(wholeFieldNine, String.valueOf(ec.getComponentSeparator()));
 165  3046
                         if (comps.length >= 3) {
 166  740
                                 messageStructure = comps[2];
 167  2306
                         } else if (comps.length > 0 && comps[0] != null && comps[0].equals("ACK")) {
 168  583
                                 messageStructure = "ACK";
 169  1723
                         } else if (comps.length == 2) {
 170  1723
                                 explicityDefined = false;
 171  1723
                                 messageStructure = comps[0] + "_" + comps[1];
 172  
                         }
 173  
                         /*
 174  
                          * else if (comps.length == 1 && comps[0] != null &&
 175  
                          * comps[0].equals("ACK")) { messageStructure = "ACK"; //it's common
 176  
                          * for people to only populate component 1 in an ACK msg }
 177  
                          */
 178  
                         else {
 179  0
                                 StringBuilder buf = new StringBuilder("Can't determine message structure from MSH-9: ");
 180  0
                                 buf.append(wholeFieldNine);
 181  0
                                 if (comps.length < 3) {
 182  0
                                         buf.append(" HINT: there are only ");
 183  0
                                         buf.append(comps.length);
 184  0
                                         buf.append(" of 3 components present");
 185  
                                 }
 186  0
                                 throw new HL7Exception(buf.toString(), ErrorCode.UNSUPPORTED_MESSAGE_TYPE);
 187  
                         }
 188  0
                 } catch (IndexOutOfBoundsException e) {
 189  0
                         throw new HL7Exception("Can't find message structure (MSH-9-3): " + e.getMessage(), ErrorCode.UNSUPPORTED_MESSAGE_TYPE);
 190  3046
                 }
 191  
 
 192  3046
                 return new MessageStructure(messageStructure, explicityDefined);
 193  
         }
 194  
 
 195  
         /**
 196  
          * Returns object that contains the field separator and encoding characters
 197  
          * for this message.
 198  
          *
 199  
          * There's an additional character starting with v2.7 (truncation), but we will
 200  
          * accept it in messages of any version.
 201  
          * 
 202  
          * @throws HL7Exception
 203  
          */
 204  
         private static EncodingCharacters getEncodingChars(String message) throws HL7Exception {
 205  14895
                 if (message.length() < 9) {
 206  10
                         throw new HL7Exception("Invalid message content: \"" + message + "\"");
 207  
                 }
 208  14885
                 return new EncodingCharacters(message.charAt(3), message.substring(4, 9));
 209  
         }
 210  
 
 211  
         /**
 212  
          * Parses a message string and returns the corresponding Message object.
 213  
          * Unexpected segments added at the end of their group.
 214  
          * 
 215  
          * @throws HL7Exception
 216  
          *             if the message is not correctly formatted.
 217  
          * @throws EncodingNotSupportedException
 218  
          *             if the message encoded is not supported by this parser.
 219  
          */
 220  
         protected Message doParse(String message, String version) throws HL7Exception {
 221  
 
 222  
                 // try to instantiate a message object of the right class
 223  2801
                 MessageStructure structure = getStructure(message);
 224  2801
                 Message m = instantiateMessage(structure.messageStructure, version, structure.explicitlyDefined);
 225  2801
                 m.setParser(this);
 226  2801
                 parse(m, message);
 227  2764
                 return m;
 228  
         }
 229  
 
 230  
         /**
 231  
          * {@inheritDoc}
 232  
          */
 233  
         protected Message doParseForSpecificPackage(String message, String version, String packageName) throws HL7Exception {
 234  
 
 235  
                 // try to instantiate a message object of the right class
 236  0
                 MessageStructure structure = getStructure(message);
 237  0
                 Message m = instantiateMessageInASpecificPackage(structure.messageStructure, version, structure.explicitlyDefined, packageName);
 238  
 
 239  0
                 parse(m, message);
 240  
 
 241  0
                 return m;
 242  
         }
 243  
 
 244  
         /**
 245  
          * Generates (or returns the cached value of) the message
 246  
          */
 247  
         private IStructureDefinition getStructureDefinition(Message theMessage) throws HL7Exception {
 248  
 
 249  3391
                 Class<? extends Message> clazz = theMessage.getClass();
 250  3391
                 HashMap<String, StructureDefinition> definitions = myStructureDefinitions.get(clazz);
 251  
 
 252  
                 StructureDefinition retVal;
 253  3391
                 if (definitions != null) {
 254  1988
                         retVal = definitions.get(theMessage.getName());
 255  1988
                         if (retVal != null) {
 256  1988
                                 return retVal;
 257  
                         }
 258  
                 }
 259  
 
 260  1403
                 if (theMessage instanceof SuperStructure) {
 261  245
                         Set<String> appliesTo = ((SuperStructure) theMessage).getStructuresWhichChildAppliesTo("MSH");
 262  245
                         if (!appliesTo.contains(theMessage.getName())) {
 263  0
                                 throw new HL7Exception("Superstructure " + theMessage.getClass().getSimpleName() + " does not apply to message " + theMessage.getName() + ", can not parse.");
 264  
                         }
 265  
                 }
 266  
                 
 267  1403
                 if (clazz.isAnnotationPresent(DoNotCacheStructure.class)) {
 268  0
                         Holder<StructureDefinition> previousLeaf = new Holder<StructureDefinition>();
 269  0
                         retVal = createStructureDefinition(theMessage, previousLeaf, theMessage.getName());
 270  0
                 } else {
 271  1403
                         Message message = ReflectionUtil.instantiateMessage(clazz, getFactory());
 272  1403
                         Holder<StructureDefinition> previousLeaf = new Holder<StructureDefinition>();
 273  1403
                         retVal = createStructureDefinition(message, previousLeaf, theMessage.getName());
 274  
 
 275  1403
                         if (!myStructureDefinitions.containsKey(clazz)) {
 276  1380
                                 myStructureDefinitions.put(clazz, new HashMap<String, StructureDefinition>());
 277  
                         }
 278  1403
                         myStructureDefinitions.get(clazz).put(theMessage.getName(), retVal);
 279  
                 }
 280  
 
 281  1403
                 return retVal;
 282  
         }
 283  
 
 284  
         private StructureDefinition createStructureDefinition(Structure theStructure, Holder<StructureDefinition> thePreviousLeaf, String theStructureName) throws HL7Exception {
 285  
 
 286  26856
                 StructureDefinition retVal = new StructureDefinition();
 287  26856
                 retVal.setName(theStructure.getName());
 288  
 
 289  26856
                 if (theStructure instanceof Group) {
 290  5033
                         retVal.setSegment(false);
 291  5033
                         Group group = (Group) theStructure;
 292  5033
                         int index = 0;
 293  5033
                         List<String> childNames = Arrays.asList(group.getNames());
 294  
                         
 295  
                         /*
 296  
                          * For SuperStructures, which can hold more than one type of structure,
 297  
                          * we only actually bring in the child names that are actually a part
 298  
                          * of the structure we are trying to parse
 299  
                          */
 300  5033
                         if (theStructure instanceof SuperStructure) {
 301  245
                                 String struct = theStructureName;
 302  245
                                 Map<String, String> evtMap = new DefaultModelClassFactory().getEventMapForVersion(Version.versionOf(theStructure.getMessage().getVersion()));
 303  245
                                 if (evtMap.containsKey(struct)) {
 304  15
                                         struct = evtMap.get(struct);
 305  
                                 }
 306  245
                                 childNames = ((SuperStructure) theStructure).getChildNamesForStructure(struct);
 307  
                         }
 308  
                         
 309  5033
                         for (String nextName : childNames) {
 310  25453
                                 Structure nextChild = group.get(nextName);
 311  25453
                                 StructureDefinition structureDefinition = createStructureDefinition(nextChild, thePreviousLeaf, theStructureName);
 312  25453
                                 structureDefinition.setNameAsItAppearsInParent(nextName);
 313  25453
                                 structureDefinition.setRepeating(group.isRepeating(nextName));
 314  25453
                                 structureDefinition.setRequired(group.isRequired(nextName));
 315  25453
                                 structureDefinition.setChoiceElement(group.isChoiceElement(nextName));
 316  25453
                                 structureDefinition.setPosition(index++);
 317  25453
                                 structureDefinition.setParent(retVal);
 318  25453
                                 retVal.addChild(structureDefinition);
 319  25453
                         }
 320  5033
                 } else {
 321  21823
                         if (thePreviousLeaf.getObject() != null) {
 322  20420
                                 thePreviousLeaf.getObject().setNextLeaf(retVal);
 323  
                         }
 324  21823
                         thePreviousLeaf.setObject(retVal);
 325  21823
                         retVal.setSegment(true);
 326  
                 }
 327  
 
 328  26856
                 return retVal;
 329  
         }
 330  
 
 331  
         /**
 332  
          * Parses a segment string and populates the given Segment object.
 333  
          * Unexpected fields are added as Varies' at the end of the segment.
 334  
      *
 335  
          * @param destination segment to parse the segment string into
 336  
      * @param segment encoded segment
 337  
      * @param encodingChars encoding characters to be used
 338  
          * @throws HL7Exception
 339  
          *             if the given string does not contain the given segment or if
 340  
          *             the string is not encoded properly
 341  
          */
 342  
         public void parse(Segment destination, String segment, EncodingCharacters encodingChars) throws HL7Exception {
 343  2010
                 parse(destination, segment, encodingChars, 0);
 344  2010
         }
 345  
 
 346  
         /**
 347  
          * Parses a segment string and populates the given Segment object.
 348  
          * Unexpected fields are added as Varies' at the end of the segment.
 349  
          *
 350  
      * @param destination segment to parse the segment string into
 351  
      * @param segment encoded segment
 352  
      * @param encodingChars encoding characters to be used
 353  
          * @param theRepetition the repetition number of this segment within its group
 354  
          * @throws HL7Exception
 355  
          *             if the given string does not contain the given segment or if
 356  
          *             the string is not encoded properly
 357  
          */
 358  
         public void parse(Segment destination, String segment, EncodingCharacters encodingChars, int theRepetition) throws HL7Exception {
 359  13849
                 int fieldOffset = 0;
 360  13849
                 if (isDelimDefSegment(destination.getName())) {
 361  3360
                         fieldOffset = 1;
 362  
                         // set field 1 to fourth character of string
 363  3360
                         Terser.set(destination, 1, 0, 1, 1, String.valueOf(encodingChars.getFieldSeparator()));
 364  
                 }
 365  
 
 366  13849
                 String[] fields = split(segment, String.valueOf(encodingChars.getFieldSeparator()));
 367  
                 // destination.setName(fields[0]);
 368  175668
                 for (int i = 1; i < fields.length; i++) {
 369  161854
                         String[] reps = split(fields[i], String.valueOf(encodingChars.getRepetitionSeparator()));
 370  
 
 371  
                         // MSH-2 will get split incorrectly so we have to fudge it ...
 372  161854
                         boolean isMSH2 = isDelimDefSegment(destination.getName()) && i + fieldOffset == 2;
 373  161854
                         if (isMSH2) {
 374  3360
                                 reps = new String[1];
 375  3360
                                 reps[0] = fields[i];
 376  
                         }
 377  
 
 378  235405
                         for (int j = 0; j < reps.length; j++) {
 379  
                                 try {
 380  73586
                                         log.trace("Parsing field {} repetition {}", i + fieldOffset, j);
 381  73586
                                         Type field = destination.getField(i + fieldOffset, j);
 382  73586
                                         if (isMSH2) {
 383  3360
                                                 Terser.getPrimitive(field, 1, 1).setValue(reps[j]);
 384  
                                         } else {
 385  70226
                                                 parse(field, reps[j], encodingChars);
 386  
                                         }
 387  35
                                 } catch (HL7Exception e) {
 388  
                                         // set the field location and throw again ...
 389  35
                                         e.setFieldPosition(i);
 390  35
                                         if (theRepetition > 1) {
 391  0
                                                 e.setSegmentRepetition(theRepetition);
 392  
                                         }
 393  35
                                         e.setSegmentName(destination.getName());
 394  35
                                         throw e;
 395  73551
                                 }
 396  
                         }
 397  
                 }
 398  
 
 399  
                 // set data type of OBX-5
 400  13814
                 if (destination.getClass().getName().contains("OBX")) {
 401  875
                         FixFieldDataType.fixOBX5(destination, getFactory(), getHapiContext().getParserConfiguration());
 402  
                 }
 403  
         // set data type of MFE-4
 404  13809
         if (destination.getClass().getName().contains("MFE") &&
 405  0
                 Version.versionOf(destination.getMessage().getVersion()).isGreaterThan(Version.V23)) {
 406  0
             FixFieldDataType.fixMFE4(destination, getFactory(), getHapiContext().getParserConfiguration());
 407  
         }
 408  
 
 409  13809
         }
 410  
 
 411  
         /**
 412  
          * @return true if the segment is MSH, FHS, or BHS. These need special
 413  
          *         treatment because they define delimiters.
 414  
          * @param theSegmentName
 415  
          *            segment name
 416  
          */
 417  
         private static boolean isDelimDefSegment(String theSegmentName) {
 418  245022
                 boolean is = false;
 419  245022
                 if (theSegmentName.equals("MSH") || theSegmentName.equals("FHS") || theSegmentName.equals("BHS")) {
 420  71816
                         is = true;
 421  
                 }
 422  245022
                 return is;
 423  
         }
 424  
 
 425  
         /**
 426  
          * Fills a field with values from an unparsed string representing the field.
 427  
          * 
 428  
          * @param destinationField
 429  
          *            the field Type
 430  
          * @param data
 431  
          *            the field string (including all components and subcomponents;
 432  
          *            not including field delimiters)
 433  
          * @param encodingCharacters
 434  
          *            the encoding characters used in the message
 435  
          */
 436  
         @Override
 437  
         public void parse(Type destinationField, String data, EncodingCharacters encodingCharacters) throws HL7Exception {
 438  70436
                 String[] components = split(data, String.valueOf(encodingCharacters.getComponentSeparator()));
 439  194404
                 for (int i = 0; i < components.length; i++) {
 440  124003
                         String[] subcomponents = split(components[i], String.valueOf(encodingCharacters.getSubcomponentSeparator()));
 441  222056
                         for (int j = 0; j < subcomponents.length; j++) {
 442  98088
                                 String val = subcomponents[j];
 443  98088
                                 if (val != null) {
 444  97748
                                         val = getParserConfiguration().getEscaping().unescape(val, encodingCharacters);
 445  
                                 }
 446  98088
                                 Terser.getPrimitive(destinationField, i + 1, j + 1).setValue(val);
 447  
                         }
 448  
                 }
 449  70401
         }
 450  
 
 451  
         /**
 452  
          * Splits the given composite string into an array of components using the
 453  
          * given delimiter.
 454  
      *
 455  
      * @param composite encoded composite string
 456  
      * @param delim delimiter to split upon
 457  
      * @return split string
 458  
          */
 459  
         public static String[] split(String composite, String delim) {
 460  385572
                 ArrayList<String> components = new ArrayList<String>();
 461  
 
 462  
                 // defend against evil nulls
 463  385572
                 if (composite == null)
 464  117368
                         composite = "";
 465  385572
                 if (delim == null)
 466  0
                         delim = "";
 467  
 
 468  385572
                 StringTokenizer tok = new StringTokenizer(composite, delim, true);
 469  385572
                 boolean previousTokenWasDelim = true;
 470  1294539
                 while (tok.hasMoreTokens()) {
 471  908967
                         String thisTok = tok.nextToken();
 472  908967
                         if (thisTok.equals(delim)) {
 473  430361
                                 if (previousTokenWasDelim)
 474  211528
                                         components.add(null);
 475  430361
                                 previousTokenWasDelim = true;
 476  
                         } else {
 477  478606
                                 components.add(thisTok);
 478  478606
                                 previousTokenWasDelim = false;
 479  
                         }
 480  908967
                 }
 481  
 
 482  385572
                 String[] ret = new String[components.size()];
 483  1075706
                 for (int i = 0; i < components.size(); i++) {
 484  690134
                         ret[i] = components.get(i);
 485  
                 }
 486  
 
 487  385572
                 return ret;
 488  
         }
 489  
 
 490  
         /**
 491  
          * {@inheritDoc }
 492  
          */
 493  
         @Override
 494  
         public String doEncode(Segment structure, EncodingCharacters encodingCharacters) throws HL7Exception {
 495  4680
                 return encode(structure, encodingCharacters, getParserConfiguration(), null);
 496  
         }
 497  
 
 498  
         /**
 499  
          * {@inheritDoc }
 500  
          */
 501  
         @Override
 502  
         public String doEncode(Type type, EncodingCharacters encodingCharacters) throws HL7Exception {
 503  335
                 return encode(type, encodingCharacters, getParserConfiguration(), null);
 504  
         }
 505  
 
 506  
         /**
 507  
          * Encodes the given Type, using the given encoding characters. It is
 508  
          * assumed that the Type represents a complete field rather than a
 509  
          * component.
 510  
      *
 511  
      * @param source type to be encoded
 512  
      * @param encodingChars encoding characters to be used
 513  
      * @return encoded type
 514  
          */
 515  
         public static String encode(Type source, EncodingCharacters encodingChars) {
 516  2870
                 return encode(source, encodingChars, source.getMessage().getParser().getParserConfiguration(), null);
 517  
         }
 518  
 
 519  
         private static String encode(Type source, EncodingCharacters encodingChars, ParserConfiguration parserConfig, String currentTerserPath) {
 520  46480
                 if (source instanceof Varies) {
 521  925
                         Varies varies = (Varies) source;
 522  925
                         if (varies.getData() != null) {
 523  925
                                 source = varies.getData();
 524  
                         }
 525  
                 }
 526  
 
 527  46480
                 StringBuilder field = new StringBuilder();
 528  206622
                 for (int i = 1; i <= Terser.numComponents(source); i++) {
 529  160142
                         StringBuilder comp = new StringBuilder();
 530  413618
                         for (int j = 1; j <= Terser.numSubComponents(source, i); j++) {
 531  253476
                                 Primitive p = Terser.getPrimitive(source, i, j);
 532  253476
                                 comp.append(encodePrimitive(p, parserConfig.getEscaping(), encodingChars));
 533  253476
                                 comp.append(encodingChars.getSubcomponentSeparator());
 534  
                         }
 535  160142
                         field.append(stripExtraDelimiters(comp.toString(), encodingChars.getSubcomponentSeparator()));
 536  160142
                         field.append(encodingChars.getComponentSeparator());
 537  
                 }
 538  
 
 539  46480
                 int forceUpToFieldNum = 0;
 540  46480
                 if (parserConfig != null && currentTerserPath != null) {
 541  43275
                         for (String nextPath : parserConfig.getForcedEncode()) {
 542  490
                                 if (nextPath.startsWith(currentTerserPath + "-") && nextPath.length() > currentTerserPath.length()) {
 543  35
                                         int endOfFieldDef = nextPath.indexOf('-', currentTerserPath.length());
 544  35
                                         if (endOfFieldDef == -1) {
 545  0
                                                 forceUpToFieldNum = 0;
 546  0
                                                 break;
 547  
                                         }
 548  35
                                         String fieldNumString = nextPath.substring(endOfFieldDef + 1, nextPath.length());
 549  35
                                         if (fieldNumString.length() > 0) {
 550  35
                                                 forceUpToFieldNum = Math.max(forceUpToFieldNum, Integer.parseInt(fieldNumString));
 551  
                                         }
 552  
                                 }
 553  490
                         }
 554  
                 }
 555  
 
 556  46480
                 char componentSeparator = encodingChars.getComponentSeparator();
 557  46480
                 String retVal = stripExtraDelimiters(field.toString(), componentSeparator);
 558  
 
 559  52060
                 while (forceUpToFieldNum > 0 && (countInstancesOf(retVal, componentSeparator) + 1) < forceUpToFieldNum) {
 560  5580
                         retVal = retVal + componentSeparator;
 561  
                 }
 562  
 
 563  46480
                 return retVal;
 564  
         }
 565  
 
 566  
         private static String encodePrimitive(Primitive p, Escaping escaping, EncodingCharacters encodingChars) {
 567  253476
                 String val = (p).getValue();
 568  253476
                 if (val == null) {
 569  197895
                         val = "";
 570  
                 } else {
 571  55581
                         val = escaping.escape(val, encodingChars);
 572  
                 }
 573  253476
                 return val;
 574  
         }
 575  
 
 576  
         /**
 577  
          * Removes unecessary delimiters from the end of a field or segment. This
 578  
          * seems to be more convenient than checking to see if they are needed while
 579  
          * we are building the encoded string.
 580  
          */
 581  
         private static String stripExtraDelimiters(String in, char delim) {
 582  219644
                 char[] chars = in.toCharArray();
 583  
 
 584  
                 // search from back end for first occurance of non-delimiter ...
 585  219644
                 int c = chars.length - 1;
 586  219644
                 boolean found = false;
 587  962640
                 while (c >= 0 && !found) {
 588  742996
                         if (chars[c--] != delim)
 589  106787
                                 found = true;
 590  
                 }
 591  
 
 592  219644
                 String ret = "";
 593  219644
                 if (found)
 594  106787
                         ret = String.valueOf(chars, 0, c + 2);
 595  219644
                 return ret;
 596  
         }
 597  
 
 598  
         /**
 599  
          * Formats a Message object into an HL7 message string using the given
 600  
          * encoding.
 601  
          * 
 602  
          * @throws HL7Exception
 603  
          *             if the data fields in the message do not permit encoding
 604  
          *             (e.g. required fields are null)
 605  
          * @throws EncodingNotSupportedException
 606  
          *             if the requested encoding is not supported by this parser.
 607  
          */
 608  
         protected String doEncode(Message source, String encoding) throws HL7Exception {
 609  263
                 if (!this.supportsEncoding(encoding))
 610  0
                         throw new EncodingNotSupportedException("This parser does not support the " + encoding + " encoding");
 611  
 
 612  263
                 return encode(source);
 613  
         }
 614  
 
 615  
         /**
 616  
          * Formats a Message object into an HL7 message string using this parser's
 617  
          * default encoding ("VB").
 618  
          * 
 619  
          * @throws HL7Exception
 620  
          *             if the data fields in the message do not permit encoding
 621  
          *             (e.g. required fields are null)
 622  
          */
 623  
         protected String doEncode(Message source) throws HL7Exception {
 624  
                 // get encoding characters ...
 625  2386
                 Segment msh = (Segment) source.get("MSH");
 626  2386
                 String fieldSepString = Terser.get(msh, 1, 0, 1, 1);
 627  
 
 628  2386
                 if (fieldSepString == null)
 629  0
                         throw new HL7Exception("Can't encode message: MSH-1 (field separator) is missing");
 630  
 
 631  2386
                 char fieldSep = '|';
 632  2386
                 if (fieldSepString.length() > 0) fieldSep = fieldSepString.charAt(0);
 633  
 
 634  2386
                 EncodingCharacters en = getValidEncodingCharacters(fieldSep, msh);
 635  
 
 636  
                 // pass down to group encoding method which will operate recursively on
 637  
                 // children ...
 638  2386
                 return encode(source, en, getParserConfiguration(), "");
 639  
         }
 640  
 
 641  
         private EncodingCharacters getValidEncodingCharacters(char fieldSep, Segment msh) throws HL7Exception {
 642  2386
                 String encCharString = Terser.get(msh, 2, 0, 1, 1);
 643  
 
 644  2386
                 if (encCharString == null) {
 645  0
                         throw new HL7Exception("Can't encode message: MSH-2 (encoding characters) is missing");
 646  
                 }
 647  
 
 648  2386
                 if (Version.V27.isGreaterThan(Version.versionOf(msh.getMessage().getVersion())) && encCharString.length() != 4) {
 649  0
                         throw new HL7Exception("Encoding characters (MSH-2) value '" + encCharString + "' invalid -- must be 4 characters", ErrorCode.DATA_TYPE_ERROR);
 650  2386
                 } else if (encCharString.length() != 4 && encCharString.length() != 5) {
 651  0
                         throw new HL7Exception("Encoding characters (MSH-2) value '" + encCharString + "' invalid -- must be 4 or 5 characters", ErrorCode.DATA_TYPE_ERROR);
 652  
                 }
 653  
 
 654  2386
                 return new EncodingCharacters(fieldSep, encCharString);
 655  
         }
 656  
 
 657  
         /**
 658  
          * Returns given group serialized as a pipe-encoded string - this method is
 659  
          * called by encode(Message source, String encoding).
 660  
      *
 661  
      * @param source group to be encoded
 662  
      * @param encodingChars encoding characters to be used
 663  
      * @throws HL7Exception if an error occurred while encoding
 664  
      * @return encoded group
 665  
          */
 666  
         public static String encode(Group source, EncodingCharacters encodingChars) throws HL7Exception {
 667  0
                 return encode(source, encodingChars, source.getMessage().getParser().getParserConfiguration(), "");
 668  
         }
 669  
 
 670  
         /**
 671  
          * Returns given group serialized as a pipe-encoded string - this method is
 672  
          * called by encode(Message source, String encoding).
 673  
          */
 674  
         private static String encode(Group source, EncodingCharacters encodingChars, ParserConfiguration parserConfiguration, String currentTerserPath) throws HL7Exception {
 675  4486
                 StringBuilder result = new StringBuilder();
 676  
 
 677  4486
                 String[] names = source.getNames();
 678  
 
 679  4486
                 String firstMandatorySegmentName = null;
 680  4486
                 boolean haveEncounteredMandatorySegment = false;
 681  4486
                 boolean haveEncounteredContent = false;
 682  4486
                 boolean haveHadMandatorySegment = false;
 683  4486
                 boolean haveHadSegmentBeforeMandatorySegment = false;
 684  
 
 685  30152
                 for (String nextName : names) {
 686  
 
 687  
                         // source.get(nextName, 0);
 688  25666
                         Structure[] reps = source.getAll(nextName);
 689  25666
                         boolean nextNameIsRequired = source.isRequired(nextName);
 690  
 
 691  25666
                         boolean havePreviouslyEncounteredMandatorySegment = haveEncounteredMandatorySegment;
 692  25666
                         haveEncounteredMandatorySegment |= nextNameIsRequired;
 693  25666
                         if (nextNameIsRequired && !haveHadMandatorySegment) {
 694  4426
                                 if (!source.isGroup(nextName)) {
 695  3616
                                         firstMandatorySegmentName = nextName;
 696  
                                 }
 697  
                         }
 698  
 
 699  25666
                         String nextTerserPath = currentTerserPath.length() > 0 ? currentTerserPath + "/" + nextName : nextName;
 700  
 
 701  
                         // Add all reps of the next segment/group
 702  36108
                         for (Structure rep : reps) {
 703  
 
 704  10442
                                 if (rep instanceof Group) {
 705  
 
 706  2100
                                         String encodedGroup = encode((Group) rep, encodingChars, parserConfiguration, nextTerserPath);
 707  2100
                                         result.append(encodedGroup);
 708  
 
 709  2100
                                         if (encodedGroup.length() > 0) {
 710  2055
                                                 if (!haveHadMandatorySegment && !haveEncounteredMandatorySegment) {
 711  600
                                                         haveHadSegmentBeforeMandatorySegment = true;
 712  
                                                 }
 713  2055
                                                 if (nextNameIsRequired && !haveHadMandatorySegment && !havePreviouslyEncounteredMandatorySegment) {
 714  185
                                                         haveHadMandatorySegment = true;
 715  
                                                 }
 716  2055
                                                 haveEncounteredContent = true;
 717  
                                         }
 718  
 
 719  2100
                                 } else {
 720  
 
 721  
                                         // Check if we are configured to force the encoding of this
 722  
                                         // segment
 723  8342
                                         boolean encodeEmptySegments = parserConfiguration.determineForcedEncodeIncludesTerserPath(nextTerserPath);
 724  8342
                                         String segString = encode((Segment) rep, encodingChars, parserConfiguration, nextTerserPath);
 725  8342
                                         if (segString.length() >= 4 || encodeEmptySegments) {
 726  6282
                                                 result.append(segString);
 727  
 
 728  6282
                                                 if (segString.length() == 3) {
 729  5
                                                         result.append(encodingChars.getFieldSeparator());
 730  
                                                 }
 731  
 
 732  6282
                                                 result.append(SEGMENT_DELIMITER);
 733  
 
 734  6282
                                                 haveEncounteredContent = true;
 735  
 
 736  6282
                                                 if (nextNameIsRequired) {
 737  4939
                                                         haveHadMandatorySegment = true;
 738  
                                                 }
 739  
 
 740  6282
                                                 if (!haveHadMandatorySegment && !haveEncounteredMandatorySegment) {
 741  425
                                                         haveHadSegmentBeforeMandatorySegment = true;
 742  
                                                 }
 743  
 
 744  
                                         }
 745  
 
 746  
                                 }
 747  
 
 748  
                         }
 749  
 
 750  
                 }
 751  
 
 752  4486
                 if (firstMandatorySegmentName != null && !haveHadMandatorySegment && !haveHadSegmentBeforeMandatorySegment && haveEncounteredContent && parserConfiguration.isEncodeEmptyMandatorySegments()) {
 753  80
                         return firstMandatorySegmentName.substring(0, 3) + encodingChars.getFieldSeparator() + SEGMENT_DELIMITER + result;
 754  
                 } else {
 755  4406
                         return result.toString();
 756  
                 }
 757  
         }
 758  
 
 759  
         /**
 760  
          * Convenience factory method which returns an instance that has a new
 761  
          * {@link DefaultHapiContext} initialized with a {@link NoValidation
 762  
          * NoValidation validation context}.
 763  
      *
 764  
      * @return PipeParser with disabled validation
 765  
          */
 766  
         public static PipeParser getInstanceWithNoValidation() {
 767  115
                 HapiContext context = new DefaultHapiContext();
 768  115
                 context.setValidationContext(ValidationContextFactory.noValidation());
 769  115
                 return new PipeParser(context);
 770  
         }
 771  
 
 772  
     /**
 773  
      * Returns given segment serialized as a pipe-encoded string.
 774  
      *
 775  
      * @param source segment to be encoded
 776  
      * @param encodingChars encoding characters to be used
 777  
      * @return encoded group
 778  
      */
 779  
         public static String encode(Segment source, EncodingCharacters encodingChars) {
 780  0
                 return encode(source, encodingChars, source.getMessage().getParser().getParserConfiguration(), null);
 781  
         }
 782  
 
 783  
         private static String encode(Segment source, EncodingCharacters encodingChars, ParserConfiguration parserConfig, String currentTerserPath) {
 784  13022
                 StringBuilder result = new StringBuilder();
 785  13022
                 result.append(source.getName());
 786  13022
                 result.append(encodingChars.getFieldSeparator());
 787  
 
 788  
                 // start at field 2 for MSH segment because field 1 is the field
 789  
                 // delimiter
 790  13022
                 int startAt = 1;
 791  13022
                 if (isDelimDefSegment(source.getName()))
 792  2881
                         startAt = 2;
 793  
 
 794  
                 // loop through fields; for every field delimit any repetitions and add
 795  
                 // field delimiter after ...
 796  13022
                 int numFields = source.numFields();
 797  
 
 798  13022
                 int forceUpToFieldNum = 0;
 799  13022
                 if (parserConfig != null && currentTerserPath != null) {
 800  8342
                         forceUpToFieldNum = parserConfig.determineForcedFieldNumForTerserPath(currentTerserPath);
 801  
                 }
 802  13022
                 numFields = Math.max(numFields, forceUpToFieldNum);
 803  
 
 804  326044
                 for (int i = startAt; i <= numFields; i++) {
 805  
 
 806  313022
                         String nextFieldTerserPath = currentTerserPath + "-" + i;
 807  313022
                         if (parserConfig != null && currentTerserPath != null) {
 808  209082
                                 for (String nextPath : parserConfig.getForcedEncode()) {
 809  22645
                                         if (nextPath.startsWith(nextFieldTerserPath + "-")) {
 810  
                                                 try {
 811  35
                                                         source.getField(i, 0);
 812  0
                                                 } catch (HL7Exception e) {
 813  0
                                                         log.error("Error while encoding segment: ", e);
 814  35
                                                 }
 815  
                                         }
 816  22645
                                 }
 817  
                         }
 818  
 
 819  
                         try {
 820  313022
                                 Type[] reps = source.getField(i);
 821  356297
                                 for (int j = 0; j < reps.length; j++) {
 822  43275
                                         String fieldText = encode(reps[j], encodingChars, parserConfig, nextFieldTerserPath);
 823  
                                         // if this is MSH-2, then it shouldn't be escaped, so
 824  
                                         // unescape it again
 825  43275
                                         if (isDelimDefSegment(source.getName()) && i == 2)
 826  2881
                                                 fieldText = parserConfig.getEscaping().unescape(fieldText, encodingChars);
 827  43275
                                         result.append(fieldText);
 828  43275
                                         if (j < reps.length - 1)
 829  485
                                                 result.append(encodingChars.getRepetitionSeparator());
 830  
                                 }
 831  0
                         } catch (HL7Exception e) {
 832  0
                                 log.error("Error while encoding segment: ", e);
 833  313022
                         }
 834  313022
                         result.append(encodingChars.getFieldSeparator());
 835  
                 }
 836  
 
 837  
                 // strip trailing delimiters ...
 838  13022
                 char fieldSeparator = encodingChars.getFieldSeparator();
 839  13022
                 String retVal = stripExtraDelimiters(result.toString(), fieldSeparator);
 840  
 
 841  13022
                 int offset = isDelimDefSegment(source.getName()) ? 1 : 0;
 842  18667
                 while (forceUpToFieldNum > 0 && (countInstancesOf(retVal, fieldSeparator) + offset) < forceUpToFieldNum) {
 843  5645
                         retVal = retVal + fieldSeparator;
 844  
                 }
 845  
 
 846  13022
                 return retVal;
 847  
         }
 848  
 
 849  
         private static int countInstancesOf(String theString, char theCharToSearchFor) {
 850  11320
                 int retVal = 0;
 851  5270355
                 for (int i = 0; i < theString.length(); i++) {
 852  5259035
                         if (theString.charAt(i) == theCharToSearchFor) {
 853  5039655
                                 retVal++;
 854  
                         }
 855  
                 }
 856  11320
                 return retVal;
 857  
         }
 858  
 
 859  
         /**
 860  
          * Removes leading whitespace from the given string. This method was created
 861  
          * to deal with frequent problems parsing messages that have been
 862  
          * hand-written in windows. The intuitive way to delimit segments is to hit
 863  
          * <ENTER> at the end of each segment, but this creates both a carriage
 864  
          * return and a line feed, so to the parser, the first character of the next
 865  
          * segment is the line feed.
 866  
      *
 867  
      * @param in input string
 868  
      * @return string with leading whitespaces removed
 869  
          */
 870  
         public static String stripLeadingWhitespace(String in) {
 871  1300
                 StringBuilder out = new StringBuilder();
 872  1300
                 char[] chars = in.toCharArray();
 873  1300
                 int c = 0;
 874  2990
                 while (c < chars.length) {
 875  2830
                         if (!Character.isWhitespace(chars[c]))
 876  1140
                                 break;
 877  1690
                         c++;
 878  
                 }
 879  94480
                 for (int i = c; i < chars.length; i++) {
 880  93180
                         out.append(chars[i]);
 881  
                 }
 882  1300
                 return out.toString();
 883  
         }
 884  
 
 885  
         /**
 886  
          * <p>
 887  
          * Returns a minimal amount of data from a message string, including only
 888  
          * the data needed to send a response to the remote system. This includes
 889  
          * the following fields:
 890  
          * <ul>
 891  
          * <li>field separator</li>
 892  
          * <li>encoding characters</li>
 893  
          * <li>processing ID</li>
 894  
          * <li>message control ID</li>
 895  
          * </ul>
 896  
          * This method is intended for use when there is an error parsing a message,
 897  
          * (so the Message object is unavailable) but an error message must be sent
 898  
          * back to the remote system including some of the information in the
 899  
          * inbound message. This method parses only that required information,
 900  
          * hopefully avoiding the condition that caused the original error. The
 901  
          * other fields in the returned MSH segment are empty.
 902  
          * </p>
 903  
          */
 904  
         public Segment getCriticalResponseData(String message) throws HL7Exception {
 905  
                 // try to get MSH segment
 906  70
                 int locStartMSH = message.indexOf("MSH");
 907  70
                 if (locStartMSH < 0)
 908  0
                         throw new HL7Exception("Couldn't find MSH segment in message: " + message, ErrorCode.SEGMENT_SEQUENCE_ERROR);
 909  70
                 int locEndMSH = message.indexOf('\r', locStartMSH + 1);
 910  70
                 if (locEndMSH < 0)
 911  0
                         locEndMSH = message.length();
 912  70
                 String mshString = message.substring(locStartMSH, locEndMSH);
 913  
 
 914  
                 // find out what the field separator is
 915  70
                 char fieldSep = mshString.charAt(3);
 916  
 
 917  
                 // get field array
 918  70
                 String[] fields = split(mshString, String.valueOf(fieldSep));
 919  
 
 920  
                 try {
 921  
                         // parse required fields
 922  70
                         String encChars = fields[1];
 923  70
                         char compSep = encChars.charAt(0);
 924  70
                         String messControlID = fields[9];
 925  70
                         String[] procIDComps = split(fields[10], String.valueOf(compSep));
 926  
 
 927  
                         // fill MSH segment
 928  70
                         String version = null;
 929  
                         try {
 930  70
                                 version = getVersion(message);
 931  0
                         } catch (Exception e) { /* use the default */
 932  70
                         }
 933  
 
 934  70
                         if (version == null) {
 935  0
                                 Version availableVersion = Version.highestAvailableVersionOrDefault();
 936  0
                                 version = availableVersion.getVersion();
 937  
                         }
 938  
 
 939  70
                         Segment msh = Parser.makeControlMSH(version, getFactory());
 940  70
                         Terser.set(msh, 1, 0, 1, 1, String.valueOf(fieldSep));
 941  70
                         Terser.set(msh, 2, 0, 1, 1, encChars);
 942  70
                         Terser.set(msh, 10, 0, 1, 1, messControlID);
 943  70
                         Terser.set(msh, 11, 0, 1, 1, procIDComps[0]);
 944  70
                         Terser.set(msh, 12, 0, 1, 1, version);
 945  70
                         return msh;
 946  
 
 947  0
                 } catch (Exception e) {
 948  0
                         throw new HL7Exception("Can't parse critical fields from MSH segment (" + e.getClass().getName() + ": " + e.getMessage() + "): " + mshString, ErrorCode.REQUIRED_FIELD_MISSING, e);
 949  
                 }
 950  
 
 951  
         }
 952  
 
 953  
         /**
 954  
          * For response messages, returns the value of MSA-2 (the message ID of the
 955  
          * message sent by the sending system). This value may be needed prior to
 956  
          * main message parsing, so that (particularly in a multi-threaded scenario)
 957  
          * the message can be routed to the thread that sent the request. We need
 958  
          * this information first so that any parse exceptions are thrown to the
 959  
          * correct thread. Returns null if MSA-2 can not be found (e.g. if the
 960  
          * message is not a response message).
 961  
          */
 962  
         public String getAckID(String message) {
 963  1216
                 String ackID = null;
 964  1216
                 int startMSA = message.indexOf("\rMSA");
 965  1216
                 if (startMSA >= 0) {
 966  578
                         int startFieldOne = startMSA + 5;
 967  578
                         char fieldDelim = message.charAt(startFieldOne - 1);
 968  578
                         int start = message.indexOf(fieldDelim, startFieldOne) + 1;
 969  578
                         int end = message.indexOf(fieldDelim, start);
 970  578
                         int segEnd = message.indexOf(String.valueOf(SEGMENT_DELIMITER), start);
 971  578
                         if (segEnd > start && segEnd < end)
 972  5
                                 end = segEnd;
 973  
 
 974  
                         // if there is no field delim after MSH-2, need to go to end of
 975  
                         // message, but not including end seg delim if it exists
 976  578
                         if (end < 0) {
 977  318
                                 if (message.charAt(message.length() - 1) == '\r') {
 978  318
                                         end = message.length() - 1;
 979  
                                 } else {
 980  0
                                         end = message.length();
 981  
                                 }
 982  
                         }
 983  578
                         if (start > 0 && end > start) {
 984  578
                                 ackID = message.substring(start, end);
 985  
                         }
 986  
                 }
 987  1216
                 log.trace("ACK ID: {}", ackID);
 988  1216
                 return ackID;
 989  
         }
 990  
 
 991  
         /**
 992  
          * Defaults to <code>false</code>
 993  
          * 
 994  
          * @see #isLegacyMode()
 995  
          * @deprecated This will be removed in HAPI 3.0
 996  
          */
 997  
         public void setLegacyMode(boolean legacyMode) {
 998  5
                 this.myLegacyMode = legacyMode;
 999  5
         }
 1000  
 
 1001  
         /**
 1002  
          * {@inheritDoc }
 1003  
          */
 1004  
         @Override
 1005  
         public String encode(Message source) throws HL7Exception {
 1006  1841
                 if (myLegacyMode != null && myLegacyMode) {
 1007  
 
 1008  
                         @SuppressWarnings("deprecation")
 1009  0
                         OldPipeParser oldPipeParser = new OldPipeParser(getFactory());
 1010  
 
 1011  0
                         return oldPipeParser.encode(source);
 1012  
                 }
 1013  1841
                 return super.encode(source);
 1014  
         }
 1015  
 
 1016  
         /**
 1017  
          * {@inheritDoc }
 1018  
          */
 1019  
         @Override
 1020  
         public Message parse(String message) throws HL7Exception {
 1021  2066
                 if (myLegacyMode != null && myLegacyMode) {
 1022  
 
 1023  
                         @SuppressWarnings("deprecation")
 1024  5
                         OldPipeParser oldPipeParser = new OldPipeParser(getFactory());
 1025  
 
 1026  5
                         return oldPipeParser.parse(message);
 1027  
                 }
 1028  2061
                 return super.parse(message);
 1029  
         }
 1030  
 
 1031  
         /**
 1032  
          * <p>
 1033  
          * Returns <code>true</code> if legacy mode is on.
 1034  
          * </p>
 1035  
          * <p>
 1036  
          * Prior to release 1.0, when an unexpected segment was encountered in a
 1037  
          * message, HAPI would recurse to the deepest nesting in the last group it
 1038  
          * encountered after the current position in the message, and deposit the
 1039  
          * segment there. This could lead to unusual behaviour where all segments
 1040  
          * afterward would not be in an expected spot within the message.
 1041  
          * </p>
 1042  
          * <p>
 1043  
          * This should normally be set to false, but any code written before the
 1044  
          * release of HAPI 1.0 which depended on this behaviour might need legacy
 1045  
          * mode to be set to true.
 1046  
          * </p>
 1047  
          * <p>
 1048  
          * Defaults to <code>false</code>. Note that this method only overrides
 1049  
          * behaviour of the {@link #parse(java.lang.String)} and
 1050  
          * {@link #encode(ca.uhn.hl7v2.model.Message) } methods
 1051  
          * </p>
 1052  
          * 
 1053  
          * @deprecated This will be removed in HAPI 3.0
 1054  
          */
 1055  
         public boolean isLegacyMode() {
 1056  0
                 if (myLegacyMode == null)
 1057  0
                         return (Boolean.parseBoolean(System.getProperty(DEFAULT_LEGACY_MODE_PROPERTY)));
 1058  0
                 return this.myLegacyMode;
 1059  
         }
 1060  
 
 1061  
         /**
 1062  
          * Returns the version ID (MSH-12) from the given message, without fully
 1063  
          * parsing the message. The version is needed prior to parsing in order to
 1064  
          * determine the message class into which the text of the message should be
 1065  
          * parsed.
 1066  
          * 
 1067  
          * @throws HL7Exception
 1068  
          *             if the version field can not be found.
 1069  
          */
 1070  
         public String getVersion(String message) throws HL7Exception {
 1071  2911
                 int startMSH = message.indexOf("MSH");
 1072  2911
                 int endMSH = message.indexOf(PipeParser.SEGMENT_DELIMITER, startMSH);
 1073  2911
                 if (endMSH < 0)
 1074  0
                         endMSH = message.length();
 1075  2911
                 String msh = message.substring(startMSH, endMSH);
 1076  
                 String fieldSep;
 1077  2911
                 if (msh.length() > 3) {
 1078  2911
                         fieldSep = String.valueOf(msh.charAt(3));
 1079  
                 } else {
 1080  0
                         throw new HL7Exception("Can't find field separator in MSH: " + msh, ErrorCode.UNSUPPORTED_VERSION_ID);
 1081  
                 }
 1082  
 
 1083  2911
                 String[] fields = split(msh, fieldSep);
 1084  
 
 1085  
                 String compSep;
 1086  2911
                 if (fields.length >= 2 && fields[1] != null && (fields[1].length() == 4 || fields[1].length() == 5)) {
 1087  2896
                         compSep = String.valueOf(fields[1].charAt(0)); // get component separator as 1st encoding char
 1088  
                 } else {
 1089  15
                         throw new HL7Exception("Invalid or incomplete encoding characters - MSH-2 is " + fields[1], ErrorCode.REQUIRED_FIELD_MISSING);
 1090  
                 }
 1091  
 
 1092  
                 String version;
 1093  2896
                 if (fields.length >= 12) {
 1094  2896
                         String[] comp = split(fields[11], compSep);
 1095  2896
                         if (comp.length >= 1) {
 1096  2896
                                 version = comp[0];
 1097  
                         } else {
 1098  0
                                 throw new HL7Exception("Can't find version ID - MSH.12 is " + fields[11], ErrorCode.REQUIRED_FIELD_MISSING);
 1099  
                         }
 1100  2896
                 } else if (getParserConfiguration().isAllowUnknownVersions()) {
 1101  0
                         return Version.highestAvailableVersionOrDefault().getVersion();
 1102  
                 } else {
 1103  0
                         throw new HL7Exception("Can't find version ID - MSH has only " + fields.length + " fields.", ErrorCode.REQUIRED_FIELD_MISSING);
 1104  
                 }
 1105  2896
                 return version;
 1106  
         }
 1107  
 
 1108  
         @Override
 1109  
         public void parse(Message message, String string) throws HL7Exception {
 1110  3391
                 if (message instanceof AbstractSuperMessage && message.getName() == null) {
 1111  245
                         String structure = getStructure(string).messageStructure;
 1112  245
                         ((AbstractSuperMessage) message).setName(structure);
 1113  
                 }
 1114  
                 
 1115  3391
                 IStructureDefinition structureDef = getStructureDefinition(message);
 1116  3391
                 MessageIterator messageIter = new MessageIterator(message, structureDef, "MSH", true);
 1117  
 
 1118  3391
                 String[] segments = split(string, SEGMENT_DELIMITER);
 1119  
 
 1120  3391
                 if (segments.length == 0) {
 1121  5
                         throw new HL7Exception("Invalid message content: \"" + string + "\"");
 1122  
                 }
 1123  
 
 1124  3386
                 if (segments[0] == null || segments[0].length() < 4) {
 1125  35
                         throw new HL7Exception("Invalid message content: \"" + string + "\"");
 1126  
                 }
 1127  
 
 1128  3351
                 char delim = '|';
 1129  3351
                 String prevName = null;
 1130  3351
                 int repNum = 1;
 1131  15380
                 for (int i = 0; i < segments.length; i++) {
 1132  
 
 1133  
                         // get rid of any leading whitespace characters ...
 1134  12086
                         if (segments[i] != null && segments[i].length() > 0 && Character.isWhitespace(segments[i].charAt(0)))
 1135  705
                                 segments[i] = stripLeadingWhitespace(segments[i]);
 1136  
 
 1137  
                         // sometimes people put extra segment delimiters at end of msg ...
 1138  12086
                         if (segments[i] != null && segments[i].length() >= 3) {
 1139  
 
 1140  
                                 final String name;
 1141  11856
                                 if (i == 0) {
 1142  3351
                                         if (segments[i].length() < 4) {
 1143  0
                                                 throw new HL7Exception("Invalid message content: \"" + string + "\"");
 1144  
                                         }
 1145  3351
                                         name = segments[i].substring(0, 3);
 1146  3351
                                         delim = segments[i].charAt(3);
 1147  
                                 } else {
 1148  8505
                                         if (segments[i].indexOf(delim) >= 0) {
 1149  8495
                                                 name = segments[i].substring(0, segments[i].indexOf(delim));
 1150  
                                         } else {
 1151  10
                                                 name = segments[i];
 1152  
                                         }
 1153  
                                 }
 1154  
 
 1155  11856
                                 log.trace("Parsing segment {}", name);
 1156  
 
 1157  11856
                                 if (name.equals(prevName)) {
 1158  965
                                         repNum++;
 1159  
                                 } else {
 1160  10891
                                         repNum = 1;
 1161  10891
                                         prevName = name;
 1162  
                                 }
 1163  
 
 1164  11856
                                 messageIter.setDirection(name);
 1165  
 
 1166  
                                 try {
 1167  11856
                                         if (messageIter.hasNext()) {
 1168  11849
                                                 Segment next = (Segment) messageIter.next();
 1169  11849
                                                 parse(next, segments[i], getEncodingChars(string), repNum);
 1170  
                                         }
 1171  5
                                 } catch (Error e) {
 1172  5
                                         if (e.getCause() instanceof HL7Exception) {
 1173  5
                                                 throw (HL7Exception)e.getCause();
 1174  
                                         }
 1175  0
                                         throw e;
 1176  11799
                                 }
 1177  
                         }
 1178  
                 }
 1179  
                 
 1180  3294
                 applySuperStructureName(message);
 1181  3294
         }
 1182  
 
 1183  
         /**
 1184  
          * A struct for holding a message class string and a boolean indicating
 1185  
          * whether it was defined explicitly.
 1186  
          */
 1187  
         private static class MessageStructure {
 1188  
                 public String messageStructure;
 1189  
                 public boolean explicitlyDefined;
 1190  
 
 1191  3046
                 public MessageStructure(String theMessageStructure, boolean isExplicitlyDefined) {
 1192  3046
                         messageStructure = theMessageStructure;
 1193  3046
                         explicitlyDefined = isExplicitlyDefined;
 1194  3046
                 }
 1195  
         }
 1196  
 
 1197  2806
         private static class Holder<T> {
 1198  
                 private T myObject;
 1199  
 
 1200  
                 public T getObject() {
 1201  42243
                         return myObject;
 1202  
                 }
 1203  
 
 1204  
                 public void setObject(T theObject) {
 1205  21823
                         myObject = theObject;
 1206  21823
                 }
 1207  
         }
 1208  
 
 1209  
 }