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