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