View Javadoc
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          VersionLogger.init();
72      }
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          super(parent);
82          this.myFactory = factory;
83          init();
84      }
85  
86      private void init() {
87          names = new ArrayList<>();
88          structures = new HashMap<>();
89          required = new HashMap<>();
90          repeating = new HashMap<>();
91          classes = new HashMap<>();
92          choiceElements = new HashSet<>();
93      }
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         return get(name, 0);
103     }
104 
105     protected <T extends Structure> T getTyped(String name, Class<T> type) {
106         try {
107             @SuppressWarnings("unchecked")
108             T ret = (T) get(name);
109             return ret;
110         } catch (HL7Exception e) {
111             log.error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
112             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         List<Structure> list = structures.get(name);
126         if (list == null)
127             throw new HL7Exception(name + " does not exist in the group " + this.getClass().getName());
128 
129         Structure ret;
130         if (rep < list.size()) {
131             // return existing Structure if it exists
132             ret = list.get(rep);
133         } else if (rep == list.size()) {
134             // verify that Structure is repeating ...
135             Boolean repeats = this.repeating.get(name);
136             if (!repeats && list.size() > 0)
137                 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             Class<? extends Structure> c = classes.get(name); // get class
142             ret = tryToInstantiateStructure(c, name);
143             list.add(ret);
144         } else {
145             StringBuilder b = new StringBuilder();
146 			b.append("Can't return repetition #");
147 			b.append(rep);
148 			b.append(" of ");
149 			b.append(name);
150 			b.append(" - there are currently ");
151 			if (list.size() == 0) {
152 				b.append("no");
153 			} else {
154 				b.append("only ");
155 				b.append(list.size());
156 			}
157 			b.append(" repetitions ");
158 			b.append("so rep# must be ");
159 			if (list.size() == 0) {
160 				b.append("0");
161 			} else {
162 				b.append("between 0 and ");
163 				b.append(list.size());
164 			}
165 			throw new HL7Exception(b.toString());
166         }
167         return ret;
168     }
169 
170     /**
171      * {@inheritDoc}
172      */
173     public boolean isEmpty() throws HL7Exception {
174         for (String name : getNames()) {
175             if (!get(name).isEmpty())
176                 return false;
177         }
178         return true;
179     }
180 
181     protected <T extends Structure> T getTyped(String name, int rep, Class<T> type) {
182         try {
183             @SuppressWarnings("unchecked")
184             T ret = (T) get(name, rep);
185             return ret;
186         } catch (HL7Exception e) {
187         	List<Structure> list = structures.get(name);
188         	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         		log.error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
192         	}
193             throw new RuntimeException(e);
194         }
195     }
196 
197     protected int getReps(String name) {
198         try {
199             return getAll(name).length;
200         } catch (HL7Exception e) {
201             String message = "Unexpected error accessing data - this is probably a bug in the source code generator.";
202             log.error(message, e);
203             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         String version = this.getMessage().getVersion();
216         if (version == null)
217             throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");
218         Class<? extends Segment> c = myFactory.getSegmentClass(name, version);
219         if (c == null)
220             c = GenericSegment.class;
221 
222         int index = this.getNames().length;
223 
224         tryToInstantiateStructure(c, name); // may throw exception
225 
226         String newName = insert(c, false, true, index, name);
227         if (this.nonStandardNames == null) {
228             this.nonStandardNames = new HashSet<>();
229         }
230         this.nonStandardNames.add(newName);
231 
232         return newName;
233     }
234 
235     public String addNonstandardSegment(String theName, int theIndex) throws HL7Exception {
236         if (this instanceof Message && theIndex == 0) {
237             throw new HL7Exception("Can not add nonstandard segment \"" + theName + "\" to start of message.");
238         }
239 
240         String version = this.getMessage().getVersion();
241         if (version == null)
242             throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");
243         Class<? extends Segment> c = myFactory.getSegmentClass(theName, version);
244 
245         if (c == null) {
246             c = GenericSegment.class;
247         }
248 
249         tryToInstantiateStructure(c, theName); // may throw exception
250 
251         String newName = insert(c, false, true, theIndex, theName);
252         if (this.nonStandardNames == null) {
253             this.nonStandardNames = new HashSet<>();
254         }
255         this.nonStandardNames.add(newName);
256 
257         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         if (nonStandardNames == null) {
268             return Collections.emptySet();
269         }
270         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         String[] retVal = new String[this.names.size()];
279         for (int i = 0; i < this.names.size(); i++) {
280             retVal[i] = this.names.get(i);
281         }
282         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     	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         String name = getName(c);
315         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         String name = getName(c);
333         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         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         if (GenericSegment.class.isAssignableFrom(c)) {
351             String genericName = name;
352             if (genericName.length() > 3) {
353                 genericName = genericName.substring(0, 3);
354             }
355             return new GenericSegment(this, genericName);
356         }
357         if (GenericGroup.class.isAssignableFrom(c)) {
358             return new GenericGroup(this, name, myFactory);
359         }
360         try {
361             return ReflectionUtil.instantiateStructure(c, this, myFactory);
362         } catch (Exception e) {
363             return ReflectionUtil.instantiate(c);
364         }
365 
366     }
367 
368 	/**
369 	 * {@inheritDoc}
370 	 */
371 	public boolean isChoiceElement(String theName) throws HL7Exception {
372 		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         Class<? extends Structure> clazz = classes.get(name);
380         if (clazz == null)
381             throw new HL7Exception("The structure " + name + " does not exist in the group "
382                     + this.getClass().getName());
383         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         Boolean req = required.get(name);
391         if (req == null)
392             throw new HL7Exception("The structure " + name + " does not exist in the group "
393                     + this.getClass().getName());
394         return req;
395     }
396 
397     /**
398      * Returns true if the named structure is required.
399      */
400     public boolean isRepeating(String name) throws HL7Exception {
401         Boolean rep = repeating.get(name);
402         if (rep == null)
403             throw new HL7Exception("The structure " + name + " does not exist in the group "
404                     + this.getClass().getName());
405         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         List<Structure> list = structures.get(name);
417         if (list == null)
418             throw new HL7Exception("The structure " + name + " does not exist in the group "
419                     + this.getClass().getName());
420         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         List<Structure> list = structures.get(name);
433         if (list == null) {
434             throw new HL7Exception("The structure " + name + " does not exist in the group "
435                     + this.getClass().getName());
436         }
437         return list.toArray(new Structure[0]);
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         Class<? extends Structure> clazz = classes.get(name);
448 
449         if (!theType.equals(clazz)) {
450             throw new HL7Exception("Structure with name \"" + name + "\" has type " + clazz.getName()
451                     + " but should be " + theType);
452         }
453         List<T> retVal = new ArrayList<>();
454         for (Structure next : structures.get(name)) {
455             retVal.add((T) next);
456         }
457         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         List<Structure> list = structures.get(name);
473         if (list == null) {
474             throw new HL7Exception("The structure " + name + " does not exist in the group "
475                     + this.getClass().getName());
476         }
477         if (list.size() == 0) {
478             throw new HL7Exception("Invalid index: " + index + ", structure " + name + " has no repetitions");
479         }
480         if (list.size() <= index) {
481             throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and "
482                     + (list.size() - 1));
483         }
484 
485         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         if (structure == null) {
498             throw new NullPointerException("Structure may not be null");
499         }
500 
501         if (structure.getMessage() != this.getMessage()) {
502             throw new HL7Exception("Structure does not belong to this message");
503         }
504 
505         List<Structure> list = structures.get(name);
506 
507         if (list == null) {
508             throw new HL7Exception("The structure " + name + " does not exist in the group "
509                     + this.getClass().getName());
510         }
511         if (list.size() < index) {
512             throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and "
513                     + (list.size()));
514         }
515 
516         list.add(index, structure);
517     }
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         if (name == null || name.length() == 0) {
532             throw new NullPointerException("Name may not be null/empty");
533         }
534 
535         Class<? extends Structure> structureClass = this.classes.get(name);
536         if (structureClass == null) {
537             throw new HL7Exception("Group " + this.getClass().getName() + " has no structure named " + name
538                     + ": Valid names: " + this.classes.keySet());
539         }
540 
541         Structure rep = tryToInstantiateStructure(structureClass, name);
542         insertRepetition(name, rep, index);
543 
544         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         int retVal = names.indexOf(name);
557         if (retVal == -1) {
558             throw new HL7Exception("Unknown name: " + name);
559         }
560         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         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         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         String name = c.getSimpleName();
582         if (Group.class.isAssignableFrom(c) && !Message.class.isAssignableFrom(c)) {
583             name = getGroupName(name);
584         }
585         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         Class<?> messageClass = getMessage().getClass();
598         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             String messageName = getName((Class<? extends Message>)messageClass);
603             if (name.startsWith(messageName) && name.length() > messageName.length()) {
604                 return name.substring(messageName.length() + 1);
605             }
606             messageClass = messageClass.getSuperclass();
607         }
608         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     	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         if (nameExists(name)) {
631             int version = 2;
632             String newName = name;
633             while (nameExists(newName)) {
634                 newName = name + version++;
635             }
636             name = newName;
637         }
638 
639         if (index > this.names.size()) {
640             throw new HL7Exception("Invalid index " + index + " - Should be <= " + this.names.size());
641         }
642 
643         this.names.add(index, name);
644         this.required.put(name, required);
645         this.repeating.put(name, repeating);
646         this.classes.put(name, c);
647         this.structures.put(name, new ArrayList<>());
648         
649         if (choiceElement) {
650         	this.choiceElements.add(name);
651         }
652 
653         return name;
654 	}
655 
656 	/**
657      * Clears all data from this structure.
658      */
659     public void clear() {
660         for (List<Structure> next : structures.values()) {
661             if (next != null) {
662                 next.clear();
663             }
664         }
665     }
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         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         if (visitor.start(this, location)) {
687             visitNestedStructures(visitor, location);
688         }
689         return visitor.end(this, location);
690     }
691     
692     public Location/../ca/uhn/hl7v2/Location.html#Location">Location provideLocation(Location location, int index, int repetition) {
693         return new Location(location).pushGroup(getName(), repetition);
694     }    
695     
696     protected void visitNestedStructures(MessageVisitor visitor, Location location) throws HL7Exception {
697         for (String name : getNames()) {
698             Structure[] structures = getAll(name);
699             for (int j=0; j < structures.length; j++) {
700                 int rep = isRepeating(name) ? j : -1;
701                 Location nextLocation = structures[j].provideLocation(location, -1, rep);
702                 if (!structures[j].accept(visitor, nextLocation)) break;
703             }
704         }        
705     }
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         String lineSeparator = System.getProperty("line.separator");
721 
722         if (theAddStartName) {
723             indent(theStringBuilder, theIndent);
724             theStringBuilder.append(getName()).append(" (start)").append(lineSeparator);
725         }
726 
727         if (theOptional || theRepeating) {
728             indent(theStringBuilder, theIndent);
729             if (theOptional) {
730                 theStringBuilder.append("[");
731             }
732             if (theRepeating) {
733                 theStringBuilder.append("{");
734             }
735             theStringBuilder.append(lineSeparator);
736         }
737 
738         boolean inChoice = false;
739         
740         for (String nextName : getNames()) {
741         	
742         	if (!thePrintEmpty) {
743         		boolean hasContent = false;
744         		Structure[] allReps = getAll(nextName);
745         		for (Structure structure : allReps) {
746 					if (!structure.isEmpty()) {
747 						hasContent = true;
748 						break;
749 					}
750 				}
751         		
752         		if (!hasContent) {
753         			continue;
754         		}
755         	}
756 
757             Class<? extends Structure> nextClass = classes.get(nextName);
758 
759             boolean nextOptional = !isRequired(nextName);
760             boolean nextRepeating = isRepeating(nextName);
761             boolean nextChoice = isChoiceElement(nextName);
762 
763             if (nextChoice && !inChoice) {
764             	theIndent += PS_INDENT;
765                 indent(theStringBuilder, theIndent);
766                 theStringBuilder.append("<");
767                 theStringBuilder.append(lineSeparator);
768                 inChoice = true;
769             } else if (!nextChoice && inChoice) {
770                 indent(theStringBuilder, theIndent);
771                 theStringBuilder.append(">");
772                 theStringBuilder.append(lineSeparator);
773                 inChoice = false;
774                 theIndent -= PS_INDENT;
775             } else if (nextChoice) {
776                 indent(theStringBuilder, theIndent);
777                 theStringBuilder.append("|");
778                 theStringBuilder.append(lineSeparator);
779             }
780             
781             if (AbstractGroup.class.isAssignableFrom(nextClass)) {
782 
783                 Structure[] nextChildren = getAll(nextName);
784                 for (int i = 0; i < nextChildren.length; i++) {
785 
786                     Structure structure = nextChildren[i];
787                     boolean addStartName = (i == 0);
788                     boolean addEndName = (i == (nextChildren.length - 1));
789                     ((AbstractGroup) structure).appendStructureDescription(theStringBuilder, theIndent + PS_INDENT,
790                             nextOptional, nextRepeating, addStartName, addEndName, thePrintEmpty);
791 
792                 }
793 
794                 if (nextChildren.length == 0) {
795                     Structure structure = tryToInstantiateStructure(nextClass, nextName);
796                     ((AbstractGroup) structure).appendStructureDescription(theStringBuilder, theIndent + PS_INDENT,
797                             nextOptional, nextRepeating, true, true, thePrintEmpty);
798                 }
799 
800             } else if (Segment.class.isAssignableFrom(nextClass)) {
801 
802                 int currentIndent = theStringBuilder.length();
803 
804                 StringBuilder structurePrefix = new StringBuilder();
805                 indent(structurePrefix, theIndent + PS_INDENT);
806                 if (nextOptional) {
807                     structurePrefix.append("[ ");
808                 }
809                 if (nextRepeating) {
810                     structurePrefix.append("{ ");
811                 }
812                 structurePrefix.append(nextName);
813                 if (nextRepeating) {
814                     structurePrefix.append(" }");
815                 }
816                 if (nextOptional) {
817                     structurePrefix.append(" ]");
818                 }
819 
820                 if (this.nonStandardNames != null && this.nonStandardNames.contains(nextName)) {
821                     structurePrefix.append(" (non-standard)");
822                 }
823                 structurePrefix.append(" - ");
824 
825                 currentIndent = theStringBuilder.length() - currentIndent;
826                 List<Structure> nextStructureList = structures.get(nextName);
827                 theStringBuilder.append(structurePrefix);
828                 if (nextStructureList == null || nextStructureList.isEmpty()) {
829                     theStringBuilder.append("Not populated");
830                     theStringBuilder.append(lineSeparator);
831                 } else {
832                     for (int i = 0; i < nextStructureList.size(); i++) {
833                         if (i > 0) {
834                             indent(theStringBuilder, currentIndent + structurePrefix.length());
835                         }
836                         Segment../../ca/uhn/hl7v2/model/Segment.html#Segment">Segment nextSegment = (Segment) nextStructureList.get(i);
837                         theStringBuilder.append(new PipeParser().doEncode(nextSegment,
838                                 EncodingCharacters.getInstance(getMessage())));
839                         theStringBuilder.append(lineSeparator);
840 
841                     }
842                 }
843 
844             }
845         }
846 
847         if (inChoice) {
848             indent(theStringBuilder, theIndent);
849             theStringBuilder.append(">");
850             theStringBuilder.append(lineSeparator);
851             theIndent -= PS_INDENT;
852         }
853         
854         if (theOptional || theRepeating) {
855             indent(theStringBuilder, theIndent);
856             if (theRepeating) {
857                 theStringBuilder.append("}");
858             }
859             if (theOptional) {
860                 theStringBuilder.append("]");
861             }
862             theStringBuilder.append(lineSeparator);
863         }
864 
865         if (theAddEndName) {
866             indent(theStringBuilder, theIndent);
867             theStringBuilder.append(getName()).append(" (end)").append(lineSeparator);
868         }
869     }
870 
871     private void indent(StringBuilder theStringBuilder, int theIndent) {
872         for (int i = 0; i < theIndent; i++) {
873             theStringBuilder.append(' ');
874         }
875     }
876 }