Coverage Report - ca.uhn.hl7v2.model.AbstractGroup
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractGroup
83%
280/334
82%
163/198
4.268
 
 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 "AbstractGroup.java".  Description: 
 10  
 "A partial implementation of Group" 
 11  
 
 12  
 The Initial Developer of the Original Code is University Health Network. Copyright (C) 
 13  
 2001.  All Rights Reserved. 
 14  
 
 15  
 Contributor(s): ______________________________________. 
 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.model;
 29  
 
 30  
 import java.util.ArrayList;
 31  
 import java.util.Collections;
 32  
 import java.util.HashMap;
 33  
 import java.util.HashSet;
 34  
 import java.util.List;
 35  
 import java.util.Map;
 36  
 import java.util.Set;
 37  
 
 38  
 import ca.uhn.hl7v2.HL7Exception;
 39  
 import ca.uhn.hl7v2.Location;
 40  
 import ca.uhn.hl7v2.VersionLogger;
 41  
 import ca.uhn.hl7v2.parser.EncodingCharacters;
 42  
 import ca.uhn.hl7v2.parser.ModelClassFactory;
 43  
 import ca.uhn.hl7v2.parser.PipeParser;
 44  
 import ca.uhn.hl7v2.util.ReflectionUtil;
 45  
 
 46  
 /**
 47  
  * A partial implementation of Group. Subclasses correspond to specific groups of segments (and/or
 48  
  * other sub-groups) that are implicitly defined by message structures in the HL7 specification. A
 49  
  * subclass should define it's group structure by putting repeated calls to the add(...) method in
 50  
  * it's constructor. Each call to add(...) adds a specific component to the Group.
 51  
  * 
 52  
  * @author Bryan Tripp (bryan_tripp@sourceforge.net)
 53  
  */
 54  
 public abstract class AbstractGroup extends AbstractStructure implements Group {
 55  
 
 56  
     private static final int PS_INDENT = 3;
 57  
 
 58  
         private static final long serialVersionUID = 1772720246448224363L;
 59  
 
 60  
     private List<String> names;
 61  
     private Map<String, List<Structure>> structures;
 62  
     private Map<String, Boolean> required;
 63  
     private Map<String, Boolean> repeating;
 64  
     private Set<String> choiceElements;
 65  
     private Map<String, Class<? extends Structure>> classes;
 66  
 
 67  
     private Set<String> nonStandardNames;
 68  
     private final ModelClassFactory myFactory;
 69  
 
 70  
     static {
 71  5
         VersionLogger.init();
 72  5
     }
 73  
 
 74  
     /**
 75  
      * This constructor should be used by implementing classes that do not also implement Message.
 76  
      * 
 77  
      * @param parent the group to which this Group belongs.
 78  
      * @param factory the factory for classes of segments, groups, and datatypes under this group
 79  
      */
 80  
     protected AbstractGroup(Group parent, ModelClassFactory factory) {
 81  64928
         super(parent);
 82  64928
         this.myFactory = factory;
 83  64928
         init();
 84  64928
     }
 85  
 
 86  
     private void init() {
 87  64928
         names = new ArrayList<String>();
 88  64928
         structures = new HashMap<String, List<Structure>>();
 89  64928
         required = new HashMap<String, Boolean>();
 90  64928
         repeating = new HashMap<String, Boolean>();
 91  64928
         classes = new HashMap<String, Class<? extends Structure>>();
 92  64928
         choiceElements = new HashSet<String>();
 93  64928
     }
 94  
 
 95  
     /**
 96  
      * Returns the named structure. If this Structure is repeating then the first repetition is
 97  
      * returned. Creates the Structure if necessary.
 98  
      * 
 99  
      * @throws HL7Exception if the named Structure is not part of this Group.
 100  
      */
 101  
     public Structure get(String name) throws HL7Exception {
 102  242922
         return get(name, 0);
 103  
     }
 104  
 
 105  
     protected <T extends Structure> T getTyped(String name, Class<T> type) {
 106  
         try {
 107  
             @SuppressWarnings("unchecked")
 108  3595
             T ret = (T) get(name);
 109  3595
             return ret;
 110  0
         } catch (HL7Exception e) {
 111  0
             log.error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
 112  0
             throw new RuntimeException(e);
 113  
         }
 114  
     }
 115  
 
 116  
     /**
 117  
      * Returns a particular repetition of the named Structure. If the given repetition number is one
 118  
      * greater than the existing number of repetitions then a new Structure is created.
 119  
      * 
 120  
      * @throws HL7Exception if the named Structure is not part of this group, if the structure is
 121  
      *             not repeatable and the given rep is > 0, or if the given repetition number is
 122  
      *             more than one greater than the existing number of repetitions.
 123  
      */
 124  
     public Structure get(String name, int rep) throws HL7Exception {
 125  341338
         List<Structure> list = structures.get(name);
 126  341338
         if (list == null)
 127  25
             throw new HL7Exception(name + " does not exist in the group " + this.getClass().getName());
 128  
 
 129  
         Structure ret;
 130  341313
         if (rep < list.size()) {
 131  
             // return existing Structure if it exists
 132  114004
             ret = list.get(rep);
 133  227309
         } else if (rep == list.size()) {
 134  
             // verify that Structure is repeating ...
 135  227299
             Boolean repeats = this.repeating.get(name);
 136  227299
             if (!repeats && list.size() > 0)
 137  0
                 throw new HL7Exception("Can't create repetition #" + rep + " of Structure " + name
 138  
                         + " - this Structure is non-repeating so only rep 0 may be retrieved");
 139  
 
 140  
             // create a new Structure, add it to the list, and return it
 141  227299
             Class<? extends Structure> c = classes.get(name); // get class
 142  227299
             ret = tryToInstantiateStructure(c, name);
 143  227299
             list.add(ret);
 144  227299
         } else {
 145  10
             StringBuilder b = new StringBuilder();
 146  10
                         b.append("Can't return repetition #");
 147  10
                         b.append(rep);
 148  10
                         b.append(" of ");
 149  10
                         b.append(name);
 150  10
                         b.append(" - there are currently ");
 151  10
                         if (list.size() == 0) {
 152  5
                                 b.append("no");
 153  
                         } else {
 154  5
                                 b.append("only ");
 155  5
                                 b.append(list.size());
 156  
                         }
 157  10
                         b.append(" repetitions ");
 158  10
                         b.append("so rep# must be ");
 159  10
                         if (list.size() == 0) {
 160  5
                                 b.append("0");
 161  
                         } else {
 162  5
                                 b.append("between 0 and ");
 163  5
                                 b.append(list.size());
 164  
                         }
 165  10
                         throw new HL7Exception(b.toString());
 166  
         }
 167  341303
         return ret;
 168  
     }
 169  
 
 170  
     /**
 171  
      * {@inheritDoc}
 172  
      */
 173  
     public boolean isEmpty() throws HL7Exception {
 174  140
         for (String name : getNames()) {
 175  130
             if (!get(name).isEmpty())
 176  90
                 return false;
 177  
         }
 178  10
         return true;
 179  
     }
 180  
 
 181  
     protected <T extends Structure> T getTyped(String name, int rep, Class<T> type) {
 182  
         try {
 183  
             @SuppressWarnings("unchecked")
 184  910
             T ret = (T) get(name, rep);
 185  900
             return ret;
 186  10
         } catch (HL7Exception e) {
 187  10
                 List<Structure> list = structures.get(name);
 188  10
                 if (list != null && list.size() < rep) {
 189  
                         // This is programmer/user error so don't report that it's a bug in the generator
 190  
                 } else {
 191  0
                         log.error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
 192  
                 }
 193  10
             throw new RuntimeException(e);
 194  
         }
 195  
     }
 196  
 
 197  
     protected int getReps(String name) {
 198  
         try {
 199  15
             return getAll(name).length;
 200  0
         } catch (HL7Exception e) {
 201  0
             String message = "Unexpected error accessing data - this is probably a bug in the source code generator.";
 202  0
             log.error(message, e);
 203  0
             throw new RuntimeException(message);
 204  
         }
 205  
     }
 206  
 
 207  
     /**
 208  
      * Expands the group definition to include a segment that is not defined by HL7 to be part of
 209  
      * this group (eg an unregistered Z segment). The new segment is slotted at the end of the
 210  
      * group. Thenceforward if such a segment is encountered it will be parsed into this location.
 211  
      * If the segment name is unrecognized a GenericSegment is used. The segment is defined as
 212  
      * repeating and not required.
 213  
      */
 214  
     public String addNonstandardSegment(String name) throws HL7Exception {
 215  840
         String version = this.getMessage().getVersion();
 216  840
         if (version == null)
 217  0
             throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");
 218  840
         Class<? extends Segment> c = myFactory.getSegmentClass(name, version);
 219  840
         if (c == null)
 220  60
             c = GenericSegment.class;
 221  
 
 222  840
         int index = this.getNames().length;
 223  
 
 224  840
         tryToInstantiateStructure(c, name); // may throw exception
 225  
 
 226  840
         String newName = insert(c, false, true, index, name);
 227  840
         if (this.nonStandardNames == null) {
 228  830
             this.nonStandardNames = new HashSet<String>();
 229  
         }
 230  840
         this.nonStandardNames.add(newName);
 231  
 
 232  840
         return newName;
 233  
     }
 234  
 
 235  
     public String addNonstandardSegment(String theName, int theIndex) throws HL7Exception {
 236  1173
         if (this instanceof Message && theIndex == 0) {
 237  0
             throw new HL7Exception("Can not add nonstandard segment \"" + theName + "\" to start of message.");
 238  
         }
 239  
 
 240  1173
         String version = this.getMessage().getVersion();
 241  1173
         if (version == null)
 242  0
             throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");
 243  1173
         Class<? extends Segment> c = myFactory.getSegmentClass(theName, version);
 244  
 
 245  1173
         if (c == null) {
 246  350
             c = GenericSegment.class;
 247  
         }
 248  
 
 249  1173
         tryToInstantiateStructure(c, theName); // may throw exception
 250  
 
 251  1173
         String newName = insert(c, false, true, theIndex, theName);
 252  1173
         if (this.nonStandardNames == null) {
 253  503
             this.nonStandardNames = new HashSet<String>();
 254  
         }
 255  1173
         this.nonStandardNames.add(newName);
 256  
 
 257  1173
         return newName;
 258  
     }
 259  
 
 260  
     /**
 261  
      * Returns a Set containing the names of all non-standard structures which have been added to
 262  
      * this structure
 263  
      *
 264  
      * @return set of non-standard structures
 265  
      */
 266  
     public Set<String> getNonStandardNames() {
 267  30
         if (nonStandardNames == null) {
 268  25
             return Collections.emptySet();
 269  
         }
 270  5
         return Collections.unmodifiableSet(nonStandardNames);
 271  
     }
 272  
 
 273  
     /**
 274  
      * Returns an ordered array of the names of the Structures in this Group. These names can be
 275  
      * used to iterate through the group using repeated calls to <code>get(name)</code>.
 276  
      */
 277  
     public String[] getNames() {
 278  223466
         String[] retVal = new String[this.names.size()];
 279  1722816
         for (int i = 0; i < this.names.size(); i++) {
 280  1499350
             retVal[i] = this.names.get(i);
 281  
         }
 282  223466
         return retVal;
 283  
     }
 284  
 
 285  
     /**
 286  
      * Adds a new Structure (group or segment) to this Group. A place for the Structure is added to
 287  
      * the group but there are initially zero repetitions. This method should be used by the
 288  
      * constructors of implementing classes to specify which Structures the Group contains -
 289  
      * Structures should be added in the order in which they appear. Note that the class is supplied
 290  
      * instead of an instance because we want there initially to be zero instances of each structure
 291  
      * but we want the AbstractGroup code to be able to create instances as necessary to support
 292  
      * get(...) calls.
 293  
      * 
 294  
      * @return the actual name used to store this structure (may be appended with an integer if
 295  
      *         there are duplicates in the same Group).
 296  
      */
 297  
     protected String add(Class<? extends Structure> c, boolean required, boolean repeating) throws HL7Exception {
 298  226881
             return add(c, required, repeating, false);
 299  
     }
 300  
 
 301  
     /**
 302  
      * Adds a new Structure (group or segment) to this Group. A place for the Structure is added to
 303  
      * the group but there are initially zero repetitions. This method should be used by the
 304  
      * constructors of implementing classes to specify which Structures the Group contains -
 305  
      * Structures should be added in the order in which they appear. Note that the class is supplied
 306  
      * instead of an instance because we want there initially to be zero instances of each structure
 307  
      * but we want the AbstractGroup code to be able to create instances as necessary to support
 308  
      * get(...) calls.
 309  
      * 
 310  
      * @return the actual name used to store this structure (may be appended with an integer if
 311  
      *         there are duplicates in the same Group).
 312  
      */
 313  
     protected String add(Class<? extends Structure> c, boolean required, boolean repeating, boolean choiceElement) throws HL7Exception {
 314  373851
         String name = getName(c);
 315  373851
         return insert(c, required, repeating, choiceElement, this.names.size(), name);
 316  
         }
 317  
 
 318  
         /**
 319  
      * Adds a new Structure (group or segment) to this Group. A place for the Structure is added to
 320  
      * the group but there are initially zero repetitions. This method should be used by the
 321  
      * constructors of implementing classes to specify which Structures the Group contains -
 322  
      * Structures should be added in the order in which they appear. Note that the class is supplied
 323  
      * instead of an instance because we want there initially to be zero instances of each structure
 324  
      * but we want the AbstractGroup code to be able to create instances as necessary to support
 325  
      * get(...) calls.
 326  
      * 
 327  
      * @return the actual name used to store this structure (may be appended with an integer if
 328  
      *         there are duplicates in the same Group).
 329  
      */
 330  
     protected String add(Class<? extends Structure> c, boolean required, boolean repeating, int index)
 331  
             throws HL7Exception {
 332  0
         String name = getName(c);
 333  0
         return insert(c, required, repeating, index, name);
 334  
     }
 335  
 
 336  
     /**
 337  
      * Returns true if the class name is already being used.
 338  
      */
 339  
     private boolean nameExists(String name) {
 340  398729
         return this.classes.get(name) != null;
 341  
     }
 342  
 
 343  
     /**
 344  
      * Attempts to create an instance of the given class and return it as a Structure.
 345  
      * 
 346  
      * @param c the Structure implementing class
 347  
      * @param name an optional name of the structure (used by Generic structures; may be null)
 348  
      */
 349  
     protected Structure tryToInstantiateStructure(Class<? extends Structure> c, String name) throws HL7Exception {
 350  230477
         if (GenericSegment.class.isAssignableFrom(c)) {
 351  870
             String genericName = name;
 352  870
             if (genericName.length() > 3) {
 353  55
                 genericName = genericName.substring(0, 3);
 354  
             }
 355  870
             return new GenericSegment(this, genericName);
 356  
         }
 357  229607
         if (GenericGroup.class.isAssignableFrom(c)) {
 358  0
             return new GenericGroup(this, name, myFactory);
 359  
         }
 360  
         try {
 361  229607
             return ReflectionUtil.instantiateStructure(c, this, myFactory);
 362  0
         } catch (Exception e) {
 363  0
             return ReflectionUtil.instantiate(c);
 364  
         }
 365  
 
 366  
     }
 367  
 
 368  
         /**
 369  
          * {@inheritDoc}
 370  
          */
 371  
         public boolean isChoiceElement(String theName) throws HL7Exception {
 372  96079
                 return choiceElements.contains(theName);
 373  
         }
 374  
 
 375  
     /**
 376  
      * Returns true if the named structure is a group
 377  
      */
 378  
     public boolean isGroup(String name) throws HL7Exception {
 379  9636
         Class<? extends Structure> clazz = classes.get(name);
 380  9636
         if (clazz == null)
 381  0
             throw new HL7Exception("The structure " + name + " does not exist in the group "
 382  0
                     + this.getClass().getName());
 383  9636
         return Group.class.isAssignableFrom(clazz);
 384  
     }
 385  
 
 386  
     /**
 387  
      * Returns true if the named structure is required.
 388  
      */
 389  
     public boolean isRequired(String name) throws HL7Exception {
 390  65369
         Boolean req = required.get(name);
 391  65369
         if (req == null)
 392  0
             throw new HL7Exception("The structure " + name + " does not exist in the group "
 393  0
                     + this.getClass().getName());
 394  65369
         return req;
 395  
     }
 396  
 
 397  
     /**
 398  
      * Returns true if the named structure is required.
 399  
      */
 400  
     public boolean isRepeating(String name) throws HL7Exception {
 401  46948
         Boolean rep = repeating.get(name);
 402  46948
         if (rep == null)
 403  0
             throw new HL7Exception("The structure " + name + " does not exist in the group "
 404  0
                     + this.getClass().getName());
 405  46948
         return rep;
 406  
     }
 407  
 
 408  
     /**
 409  
      * Returns the number of existing repetitions of the named structure.
 410  
      *
 411  
      * @param name structure name
 412  
      * @return number of existing repetitions of the named structure
 413  
      * @throws HL7Exception if the structure is unknown
 414  
      */
 415  
     public int currentReps(String name) throws HL7Exception {
 416  0
         List<Structure> list = structures.get(name);
 417  0
         if (list == null)
 418  0
             throw new HL7Exception("The structure " + name + " does not exist in the group "
 419  0
                     + this.getClass().getName());
 420  0
         return list.size();
 421  
     }
 422  
 
 423  
     /**
 424  
      * Returns an array of Structure objects by name. For example, if the Group contains an MSH
 425  
      * segment and "MSH" is supplied then this call would return a 1-element array containing the
 426  
      * MSH segment. Multiple elements are returned when the segment or group repeats. The array may
 427  
      * be empty if no repetitions have been accessed yet using the get(...) methods.
 428  
      * 
 429  
      * @throws HL7Exception if the named Structure is not part of this Group.
 430  
      */
 431  
     public Structure[] getAll(String name) throws HL7Exception {
 432  117435
         List<Structure> list = structures.get(name);
 433  117435
         if (list == null) {
 434  10
             throw new HL7Exception("The structure " + name + " does not exist in the group "
 435  10
                     + this.getClass().getName());
 436  
         }
 437  117425
         return list.toArray(new Structure[list.size()]);
 438  
     }
 439  
 
 440  
     /**
 441  
      * Returns a list containing all existing repetitions of the structure identified by name
 442  
      * 
 443  
      * @throws HL7Exception if the named Structure is not part of this Group.
 444  
      */
 445  
     @SuppressWarnings("unchecked")
 446  
     protected <T extends Structure> List<T> getAllAsList(String name, Class<T> theType) throws HL7Exception {
 447  55
         Class<? extends Structure> clazz = classes.get(name);
 448  
 
 449  55
         if (!theType.equals(clazz)) {
 450  0
             throw new HL7Exception("Structure with name \"" + name + "\" has type " + clazz.getName()
 451  
                     + " but should be " + theType);
 452  
         }
 453  55
         List<T> retVal = new ArrayList<T>();
 454  55
         for (Structure next : structures.get(name)) {
 455  45
             retVal.add((T) next);
 456  45
         }
 457  55
         return Collections.unmodifiableList(retVal);
 458  
     }
 459  
 
 460  
     /**
 461  
      * Removes a repetition of a given Structure objects by name. For example, if the Group contains
 462  
      * 10 repititions an OBX segment and "OBX" is supplied with an index of 2, then this call would
 463  
      * remove the 3rd repetition. Note that in this case, the Set ID field in the OBX segments would
 464  
      * also need to be renumbered manually.
 465  
      *
 466  
      * @param name structure name
 467  
      * @param index repetition to remove the structure from
 468  
      * @return The removed structure
 469  
      * @throws HL7Exception if the named Structure is not part of this Group.
 470  
      */
 471  
     public Structure removeRepetition(String name, int index) throws HL7Exception {
 472  5
         List<Structure> list = structures.get(name);
 473  5
         if (list == null) {
 474  0
             throw new HL7Exception("The structure " + name + " does not exist in the group "
 475  0
                     + this.getClass().getName());
 476  
         }
 477  5
         if (list.size() == 0) {
 478  0
             throw new HL7Exception("Invalid index: " + index + ", structure " + name + " has no repetitions");
 479  
         }
 480  5
         if (list.size() <= index) {
 481  0
             throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and "
 482  0
                     + (list.size() - 1));
 483  
         }
 484  
 
 485  5
         return list.remove(index);
 486  
     }
 487  
 
 488  
     /**
 489  
      * Inserts a repetition of a given Structure into repetitions of that structure by name. For
 490  
      * example, if the Group contains 10 repetitions an OBX segment and an OBX is supplied with an
 491  
      * index of 2, then this call would insert the new repetition at index 2. (Note that in this
 492  
      * example, the Set ID field in the OBX segments would also need to be renumbered manually).
 493  
      * 
 494  
      * @throws HL7Exception if the named Structure is not part of this Group.
 495  
      */
 496  
     protected void insertRepetition(String name, Structure structure, int index) throws HL7Exception {
 497  10
         if (structure == null) {
 498  0
             throw new NullPointerException("Structure may not be null");
 499  
         }
 500  
 
 501  10
         if (structure.getMessage() != this.getMessage()) {
 502  0
             throw new HL7Exception("Structure does not belong to this message");
 503  
         }
 504  
 
 505  10
         List<Structure> list = structures.get(name);
 506  
 
 507  10
         if (list == null) {
 508  0
             throw new HL7Exception("The structure " + name + " does not exist in the group "
 509  0
                     + this.getClass().getName());
 510  
         }
 511  10
         if (list.size() < index) {
 512  0
             throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and "
 513  0
                     + (list.size()));
 514  
         }
 515  
 
 516  10
         list.add(index, structure);
 517  10
     }
 518  
 
 519  
     /**
 520  
      * Inserts a repetition of a given Structure into repetitions of that structure by name. For
 521  
      * example, if the Group contains 10 repititions an OBX segment and an OBX is supplied with an
 522  
      * index of 2, then this call would insert the new repetition at index 2. Note that in this
 523  
      * case, the Set ID field in the OBX segments would also need to be renumbered manually.
 524  
      *
 525  
      * @param name structure name
 526  
      * @param index repetition to insert the structure
 527  
      * @return The inserted structure
 528  
      * @throws HL7Exception if the named Structure is not part of this Group.
 529  
      */
 530  
     public Structure insertRepetition(String name, int index) throws HL7Exception {
 531  5
         if (name == null || name.length() == 0) {
 532  0
             throw new NullPointerException("Name may not be null/empty");
 533  
         }
 534  
 
 535  5
         Class<? extends Structure> structureClass = this.classes.get(name);
 536  5
         if (structureClass == null) {
 537  0
             throw new HL7Exception("Group " + this.getClass().getName() + " has no structure named " + name
 538  0
                     + ": Valid names: " + this.classes.keySet());
 539  
         }
 540  
 
 541  5
         Structure rep = tryToInstantiateStructure(structureClass, name);
 542  5
         insertRepetition(name, rep, index);
 543  
 
 544  5
         return rep;
 545  
     }
 546  
 
 547  
     /**
 548  
      * Given a child structure name, returns the child index (which is 1-indexed, meaning that the
 549  
      * first child is at index 1
 550  
      *
 551  
      * @param name structure name
 552  
      * @return position of the structure in this group
 553  
      * @throws HL7Exception if the structure is unknown
 554  
      */
 555  
     public int getFieldNumForName(String name) throws HL7Exception {
 556  0
         int retVal = names.indexOf(name);
 557  0
         if (retVal == -1) {
 558  0
             throw new HL7Exception("Unknown name: " + name);
 559  
         }
 560  0
         return retVal + 1;
 561  
     }
 562  
 
 563  
     /**
 564  
      * Returns the Class of the Structure at the given name index.
 565  
      */
 566  
     public Class<? extends Structure> getClass(String name) {
 567  0
         return classes.get(name);
 568  
     }
 569  
 
 570  
     /**
 571  
      * Returns the class name (excluding package).
 572  
      * 
 573  
      * @see Structure#getName()
 574  
      */
 575  
     public String getName() {
 576  51172
         return getName(getClass());
 577  
     }
 578  
 
 579  
     // returns a name for a class of a Structure in this Message
 580  
     private String getName(Class<? extends Structure> c) {        
 581  528195
         String name = c.getSimpleName();
 582  528195
         if (Group.class.isAssignableFrom(c) && !Message.class.isAssignableFrom(c)) {
 583  103117
             name = getGroupName(name);
 584  
         }
 585  528195
         return name;
 586  
     }
 587  
 
 588  
     /**
 589  
      * Remove message name prefix from group names for compatibility with getters. Due to
 590  
      * 3558962 we also need to look at the message's super classes to enable custom
 591  
      * messages that reuse groups from their ancestors.
 592  
      *
 593  
      * @param name the simple name of the group
 594  
      * @return the abbreviated group in name in case of matching prefixes
 595  
      */
 596  
     private String getGroupName(String name) {
 597  103117
         Class<?> messageClass = getMessage().getClass();
 598  103197
         while (Message.class.isAssignableFrom(messageClass)) {
 599  
             @SuppressWarnings("unchecked")
 600  
             // actually we should call getName() instead of getName(Class), but this
 601  
             // is due to issue 3558962
 602  103172
             String messageName = getName((Class<? extends Message>)messageClass);
 603  103172
             if (name.startsWith(messageName) && name.length() > messageName.length()) {
 604  103092
                 return name.substring(messageName.length() + 1);
 605  
             }
 606  80
             messageClass = messageClass.getSuperclass();
 607  80
         }
 608  25
         return name;
 609  
     }
 610  
     
 611  
 
 612  
 
 613  
     /**
 614  
      * Inserts the given structure into this group, at the indicated index number. This method is
 615  
      * used to support handling of unexpected segments (e.g. Z-segments). In contrast, specification
 616  
      * of the group's normal children should be done at construction time, using the add(...)
 617  
      * method.
 618  
      */
 619  
     protected String insert(Class<? extends Structure> c, boolean required, boolean repeating, int index, String name)
 620  
             throws HL7Exception {
 621  2013
             return insert(c, required, repeating, false, index, name);
 622  
     }
 623  
 
 624  
     protected String insert(Class<? extends Structure> c, boolean required, boolean repeating, boolean choiceElement, 
 625  
                     int index, String name) throws HL7Exception {
 626  
         // tryToInstantiateStructure(c, name); //may throw exception
 627  
 
 628  
         // see if there is already something by this name and make a new name if
 629  
         // necessary ...
 630  375864
         if (nameExists(name)) {
 631  11410
             int version = 2;
 632  11410
             String newName = name;
 633  22865
             while (nameExists(newName)) {
 634  11455
                 newName = name + version++;
 635  
             }
 636  11410
             name = newName;
 637  
         }
 638  
 
 639  375864
         if (index > this.names.size()) {
 640  0
             throw new HL7Exception("Invalid index " + index + " - Should be <= " + this.names.size());
 641  
         }
 642  
 
 643  375864
         this.names.add(index, name);
 644  375864
         this.required.put(name, required);
 645  375864
         this.repeating.put(name, repeating);
 646  375864
         this.classes.put(name, c);
 647  375864
         this.structures.put(name, new ArrayList<Structure>());
 648  
         
 649  375864
         if (choiceElement) {
 650  4835
                 this.choiceElements.add(name);
 651  
         }
 652  
 
 653  375864
         return name;
 654  
         }
 655  
 
 656  
         /**
 657  
      * Clears all data from this structure.
 658  
      */
 659  
     public void clear() {
 660  605
         for (List<Structure> next : structures.values()) {
 661  12665
             if (next != null) {
 662  12665
                 next.clear();
 663  
             }
 664  12665
         }
 665  605
     }
 666  
 
 667  
     /**
 668  
      * Returns the {@link ModelClassFactory} associated with this structure
 669  
      *
 670  
      * @return the {@link ModelClassFactory} associated with this structure
 671  
      */
 672  
     public final ModelClassFactory getModelClassFactory() {
 673  0
         return myFactory;
 674  
     }
 675  
 
 676  
     /**
 677  
      * Iterates over the contained structures and calls the visitor for each
 678  
      * of them.
 679  
      *
 680  
      * @param visitor MessageVisitor instance to be called back.
 681  
      * @param location location of the group
 682  
      * @return true if visiting shall continue, false if not
 683  
      * @throws HL7Exception
 684  
      */
 685  
     public boolean accept(MessageVisitor visitor, Location location) throws HL7Exception {
 686  50
         if (visitor.start(this, location)) {
 687  50
             visitNestedStructures(visitor, location);
 688  
         }
 689  50
         return visitor.end(this, location);
 690  
     }
 691  
     
 692  
     public Location provideLocation(Location location, int index, int repetition) {
 693  50
         return new Location(location).pushGroup(getName(), repetition);
 694  
     }    
 695  
     
 696  
     protected void visitNestedStructures(MessageVisitor visitor, Location location) throws HL7Exception {
 697  270
         for (String name : getNames()) {
 698  210
             Structure[] structures = getAll(name);
 699  340
             for (int j=0; j < structures.length; j++) {
 700  130
                 int rep = isRepeating(name) ? j : -1;
 701  130
                 Location nextLocation = structures[j].provideLocation(location, -1, rep);
 702  130
                 if (!structures[j].accept(visitor, nextLocation)) break;
 703  
             }
 704  
         }        
 705  60
     }
 706  
 
 707  
     /**
 708  
      * <p>
 709  
      * Appends a description of this group's structure and all children's structure to a string
 710  
      * builder.
 711  
      * </p>
 712  
      * <p>
 713  
      * Note that this method is intended only to be called by
 714  
      * {@link AbstractMessage#printStructure()}. Please use caution if calling this method directly,
 715  
      * as the method signature and/or behaviour may change in the future.
 716  
      * </p>
 717  
      */
 718  
     void appendStructureDescription(StringBuilder theStringBuilder, int theIndent, boolean theOptional,
 719  
             boolean theRepeating, boolean theAddStartName, boolean theAddEndName, boolean thePrintEmpty) throws HL7Exception {
 720  1810
         String lineSeparator = System.getProperty("line.separator");
 721  
 
 722  1810
         if (theAddStartName) {
 723  1775
             indent(theStringBuilder, theIndent);
 724  1775
             theStringBuilder.append(getName()).append(" (start)").append(lineSeparator);
 725  
         }
 726  
 
 727  1810
         if (theOptional || theRepeating) {
 728  1320
             indent(theStringBuilder, theIndent);
 729  1320
             if (theOptional) {
 730  1190
                 theStringBuilder.append("[");
 731  
             }
 732  1320
             if (theRepeating) {
 733  1205
                 theStringBuilder.append("{");
 734  
             }
 735  1320
             theStringBuilder.append(lineSeparator);
 736  
         }
 737  
 
 738  1810
         boolean inChoice = false;
 739  
         
 740  16090
         for (String nextName : getNames()) {
 741  
                 
 742  14280
                 if (!thePrintEmpty) {
 743  375
                         boolean hasContent = false;
 744  375
                         Structure[] allReps = getAll(nextName);
 745  375
                         for (Structure structure : allReps) {
 746  85
                                         if (!structure.isEmpty()) {
 747  85
                                                 hasContent = true;
 748  85
                                                 break;
 749  
                                         }
 750  
                                 }
 751  
                         
 752  375
                         if (!hasContent) {
 753  290
                                 continue;
 754  
                         }
 755  
                 }
 756  
 
 757  13990
             Class<? extends Structure> nextClass = classes.get(nextName);
 758  
 
 759  13990
             boolean nextOptional = !isRequired(nextName);
 760  13990
             boolean nextRepeating = isRepeating(nextName);
 761  13990
             boolean nextChoice = isChoiceElement(nextName);
 762  
 
 763  13990
             if (nextChoice && !inChoice) {
 764  5
                     theIndent += PS_INDENT;
 765  5
                 indent(theStringBuilder, theIndent);
 766  5
                 theStringBuilder.append("<");
 767  5
                 theStringBuilder.append(lineSeparator);
 768  5
                 inChoice = true;
 769  13985
             } else if (!nextChoice && inChoice) {
 770  5
                 indent(theStringBuilder, theIndent);
 771  5
                 theStringBuilder.append(">");
 772  5
                 theStringBuilder.append(lineSeparator);
 773  5
                 inChoice = false;
 774  5
                 theIndent -= PS_INDENT;
 775  13980
             } else if (nextChoice && inChoice) {
 776  25
                 indent(theStringBuilder, theIndent);
 777  25
                 theStringBuilder.append("|");
 778  25
                 theStringBuilder.append(lineSeparator);
 779  
             }
 780  
             
 781  13990
             if (AbstractGroup.class.isAssignableFrom(nextClass)) {
 782  
 
 783  1285
                 Structure[] nextChildren = getAll(nextName);
 784  1445
                 for (int i = 0; i < nextChildren.length; i++) {
 785  
 
 786  160
                     Structure structure = nextChildren[i];
 787  160
                     boolean addStartName = (i == 0);
 788  160
                     boolean addEndName = (i == (nextChildren.length - 1));
 789  160
                     ((AbstractGroup) structure).appendStructureDescription(theStringBuilder, theIndent + PS_INDENT,
 790  
                             nextOptional, nextRepeating, addStartName, addEndName, thePrintEmpty);
 791  
 
 792  
                 }
 793  
 
 794  1285
                 if (nextChildren.length == 0) {
 795  1160
                     Structure structure = tryToInstantiateStructure(nextClass, nextName);
 796  1160
                     ((AbstractGroup) structure).appendStructureDescription(theStringBuilder, theIndent + PS_INDENT,
 797  
                             nextOptional, nextRepeating, true, true, thePrintEmpty);
 798  
                 }
 799  
 
 800  1285
             } else if (Segment.class.isAssignableFrom(nextClass)) {
 801  
 
 802  12705
                 int currentIndent = theStringBuilder.length();
 803  
 
 804  12705
                 StringBuilder structurePrefix = new StringBuilder();
 805  12705
                 indent(structurePrefix, theIndent + PS_INDENT);
 806  12705
                 if (nextOptional) {
 807  9540
                     structurePrefix.append("[ ");
 808  
                 }
 809  12705
                 if (nextRepeating) {
 810  4660
                     structurePrefix.append("{ ");
 811  
                 }
 812  12705
                 structurePrefix.append(nextName);
 813  12705
                 if (nextRepeating) {
 814  4660
                     structurePrefix.append(" }");
 815  
                 }
 816  12705
                 if (nextOptional) {
 817  9540
                     structurePrefix.append(" ]");
 818  
                 }
 819  
 
 820  12705
                 if (this.nonStandardNames != null && this.nonStandardNames.contains(nextName)) {
 821  35
                     structurePrefix.append(" (non-standard)");
 822  
                 }
 823  12705
                 structurePrefix.append(" - ");
 824  
 
 825  12705
                 currentIndent = theStringBuilder.length() - currentIndent;
 826  12705
                 List<Structure> nextStructureList = structures.get(nextName);
 827  12705
                 theStringBuilder.append(structurePrefix);
 828  12705
                 if (nextStructureList == null || nextStructureList.isEmpty()) {
 829  10030
                     theStringBuilder.append("Not populated");
 830  10030
                     theStringBuilder.append(lineSeparator);
 831  
                 } else {
 832  5400
                     for (int i = 0; i < nextStructureList.size(); i++) {
 833  2725
                         if (i > 0) {
 834  50
                             indent(theStringBuilder, currentIndent + structurePrefix.length());
 835  
                         }
 836  2725
                         Segment nextSegment = (Segment) nextStructureList.get(i);
 837  5450
                         theStringBuilder.append(new PipeParser().doEncode(nextSegment,
 838  2725
                                 EncodingCharacters.getInstance(getMessage())));
 839  2725
                         theStringBuilder.append(lineSeparator);
 840  
 
 841  
                     }
 842  
                 }
 843  
 
 844  
             }
 845  
         }
 846  
 
 847  1810
         if (inChoice) {
 848  0
             indent(theStringBuilder, theIndent);
 849  0
             theStringBuilder.append(">");
 850  0
             theStringBuilder.append(lineSeparator);
 851  0
             theIndent -= PS_INDENT;
 852  
         }
 853  
         
 854  1810
         if (theOptional || theRepeating) {
 855  1320
             indent(theStringBuilder, theIndent);
 856  1320
             if (theRepeating) {
 857  1205
                 theStringBuilder.append("}");
 858  
             }
 859  1320
             if (theOptional) {
 860  1190
                 theStringBuilder.append("]");
 861  
             }
 862  1320
             theStringBuilder.append(lineSeparator);
 863  
         }
 864  
 
 865  1810
         if (theAddEndName) {
 866  1775
             indent(theStringBuilder, theIndent);
 867  1775
             theStringBuilder.append(getName()).append(" (end)").append(lineSeparator);
 868  
         }
 869  1810
     }
 870  
 
 871  
     private void indent(StringBuilder theStringBuilder, int theIndent) {
 872  88980
         for (int i = 0; i < theIndent; i++) {
 873  70000
             theStringBuilder.append(' ');
 874  
         }
 875  18980
     }
 876  
 }