001/**
002The contents of this file are subject to the Mozilla Public License Version 1.1 
003(the "License"); you may not use this file except in compliance with the License. 
004You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005Software distributed under the License is distributed on an "AS IS" basis, 
006WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007specific language governing rights and limitations under the License. 
008
009The Original Code is "AbstractGroup.java".  Description: 
010"A partial implementation of Group" 
011
012The Initial Developer of the Original Code is University Health Network. Copyright (C) 
0132001.  All Rights Reserved. 
014
015Contributor(s): ______________________________________. 
016
017Alternatively, the contents of this file may be used under the terms of the 
018GNU General Public License (the  "GPL"), in which case the provisions of the GPL are 
019applicable instead of those above.  If you wish to allow use of your version of this 
020file only under the terms of the GPL and not to allow others to use your version 
021of this file under the MPL, indicate your decision by deleting  the provisions above 
022and replace  them with the notice and other provisions required by the GPL License.  
023If you do not delete the provisions above, a recipient may use your version of 
024this file under either the MPL or the GPL. 
025
026 */
027
028package ca.uhn.hl7v2.model;
029
030import java.lang.reflect.Constructor;
031import java.util.ArrayList;
032import java.util.Collections;
033import java.util.HashMap;
034import java.util.HashSet;
035import java.util.List;
036import java.util.Map;
037import java.util.Set;
038
039import ca.uhn.hl7v2.HL7Exception;
040import ca.uhn.hl7v2.VersionLogger;
041import ca.uhn.hl7v2.parser.EncodingCharacters;
042import ca.uhn.hl7v2.parser.ModelClassFactory;
043import ca.uhn.hl7v2.parser.PipeParser;
044
045/**
046 * A partial implementation of Group. Subclasses correspond to specific groups
047 * of segments (and/or other sub-groups) that are implicitly defined by message
048 * structures in the HL7 specification. A subclass should define it's group
049 * structure by putting repeated calls to the add(...) method in it's
050 * constructor. Each call to add(...) adds a specific component to the Group.
051 * 
052 * @author Bryan Tripp (bryan_tripp@sourceforge.net)
053 */
054public abstract class AbstractGroup extends AbstractStructure implements Group {
055
056        private static final long serialVersionUID = 1772720246448224363L;
057        
058        private List<String> names;
059    private Map<String, List<Structure>> structures;
060    private Map<String, Boolean> required;
061    private Map<String, Boolean> repeating;
062    private Map<String, Class<? extends Structure>> classes;
063    // protected Message message;
064    private Set<String> nonStandardNames;
065    private final ModelClassFactory myFactory;
066
067    static {
068        VersionLogger.init();
069    }
070    
071    /**
072     * This constructor should be used by implementing classes that do not also
073     * implement Message.
074     * 
075     * @param parent
076     *            the group to which this Group belongs.
077     * @param factory
078     *            the factory for classes of segments, groups, and datatypes
079     *            under this group
080     */
081    protected AbstractGroup(Group parent, ModelClassFactory factory) {
082        super(parent);
083        this.myFactory = factory;
084        init();
085    }
086
087    private void init() {
088        names = new ArrayList<String>();
089        structures = new HashMap<String, List<Structure>>();
090        required = new HashMap<String, Boolean>();
091        repeating = new HashMap<String, Boolean>();
092        classes = new HashMap<String, Class<? extends Structure>>();
093    }
094
095    /**
096     * Returns the named structure. If this Structure is repeating then the
097     * first repetition is returned. Creates the Structure if necessary.
098     * 
099     * @throws HL7Exception
100     *             if the named Structure is not part of this Group.
101     */
102    public Structure get(String name) throws HL7Exception {
103        return get(name, 0);
104    }
105    
106    protected <T extends Structure> T getTyped(String name, Class<T> type) {
107        try {
108           @SuppressWarnings("unchecked") 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
118     * repetition number is one greater than the existing number of repetitions
119     * then a new Structure is created.
120     * 
121     * @throws HL7Exception
122     *             if the named Structure is not part of this group, if the
123     *             structure is not repeatable and the given rep is > 0, or if
124     *             the given repetition number is more than one greater than the
125     *             existing number of repetitions.
126     */
127    public Structure get(String name, int rep) throws HL7Exception {
128        List<Structure> list = structures.get(name);
129        if (list == null)
130            throw new HL7Exception(name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
131
132        Structure ret;
133        if (rep < list.size()) {
134            // return existing Structure if it exists
135            ret = list.get(rep);
136        } else if (rep == list.size()) {
137            // verify that Structure is repeating ...
138            Boolean repeats = this.repeating.get(name);
139            if (!repeats.booleanValue() && list.size() > 0)
140                throw new HL7Exception("Can't create repetition #" + rep + " of Structure " + name + " - this Structure is non-repeating", HL7Exception.APPLICATION_INTERNAL_ERROR);
141
142            // create a new Structure, add it to the list, and return it
143            Class<? extends Structure> c = classes.get(name); // get class
144            ret = tryToInstantiateStructure(c, name);
145            list.add(ret);
146        } else {
147            throw new HL7Exception("Can't return repetition #" + rep + " of " + name + " - there are only " + list.size() + " repetitions.",
148                    HL7Exception.APPLICATION_INTERNAL_ERROR);
149        }
150        return ret;
151    }
152    
153    protected <T extends Structure> T getTyped(String name, int rep, Class<T> type) {
154        try {
155           @SuppressWarnings("unchecked") T ret = (T)get(name, rep);
156           return ret;
157        } catch(HL7Exception e) {
158           log.error("Unexpected error accessing data - this is probably a bug in the source code generator.", e);
159           throw new RuntimeException(e);
160        }
161     }
162    
163    protected int getReps(String name) { 
164        try { 
165            return getAll(name).length; 
166        } catch (HL7Exception e) { 
167            String message = "Unexpected error accessing data - this is probably a bug in the source code generator."; 
168            log.error(message, e); 
169            throw new RuntimeException(message);
170        }       
171    }
172
173    /**
174     * Expands the group definition to include a segment that is not defined by
175     * HL7 to be part of this group (eg an unregistered Z segment). The new
176     * segment is slotted at the end of the group. Thenceforward if such a
177     * segment is encountered it will be parsed into this location. If the
178     * segment name is unrecognized a GenericSegment is used. The segment is
179     * defined as repeating and not required.
180     */
181    public String addNonstandardSegment(String name) throws HL7Exception {
182        String version = this.getMessage().getVersion();
183        if (version == null)
184            throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");
185        Class<? extends Structure> c = myFactory.getSegmentClass(name, version);
186        if (c == null)
187            c = GenericSegment.class;
188
189        int index = this.getNames().length;
190
191        tryToInstantiateStructure(c, name); // may throw exception
192
193        String newName = insert(c, false, true, index, name);
194        if (this.nonStandardNames == null) {
195            this.nonStandardNames = new HashSet<String>();
196        }
197        this.nonStandardNames.add(newName);
198
199        return newName;
200    }
201
202    public String addNonstandardSegment(String theName, int theIndex) throws HL7Exception {
203        if (this instanceof Message && theIndex == 0) {
204            throw new HL7Exception("Can not add nonstandard segment \"" + theName + "\" to start of message.");
205        }
206
207        String version = this.getMessage().getVersion();
208        if (version == null)
209            throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");
210        Class<? extends Structure> c = myFactory.getSegmentClass(theName, version);
211        
212        if (c == null) {
213            c = GenericSegment.class;
214        }
215
216        tryToInstantiateStructure(c, theName); // may throw exception
217
218        String newName = insert(c, false, true, theIndex, theName);
219        if (this.nonStandardNames == null) {
220            this.nonStandardNames = new HashSet<String>();
221        }
222        this.nonStandardNames.add(newName);
223
224        return newName;
225    }
226
227    /**
228     * Returns a Set containing the names of all non-standard structures
229     * which have been added to this structure
230     */
231    public Set<String> getNonStandardNames() {
232        if (nonStandardNames == null) {
233                return Collections.emptySet();
234        }
235        return Collections.unmodifiableSet(nonStandardNames);
236    }
237    
238    /**
239     * Returns an ordered array of the names of the Structures in this Group.
240     * These names can be used to iterate through the group using repeated calls
241     * to <code>get(name)</code>.
242     */
243    public String[] getNames() {
244        String[] retVal = new String[this.names.size()];
245        for (int i = 0; i < this.names.size(); i++) {
246            retVal[i] = this.names.get(i);
247        }
248        return retVal;
249    }
250
251    /**
252     * Adds a new Structure (group or segment) to this Group. A place for the
253     * Structure is added to the group but there are initially zero repetitions.
254     * This method should be used by the constructors of implementing classes to
255     * specify which Structures the Group contains - Structures should be added
256     * in the order in which they appear. Note that the class is supplied
257     * instead of an instance because we want there initially to be zero
258     * instances of each structure but we want the AbstractGroup code to be able
259     * to create instances as necessary to support get(...) calls.
260     * 
261     * @return the actual name used to store this structure (may be appended
262     *         with an integer if there are duplicates in the same Group).
263     */
264    protected String add(Class<? extends Structure> c, boolean required, boolean repeating) throws HL7Exception {
265        String name = getName(c);
266        return insert(c, required, repeating, this.names.size(), name);
267    }
268
269    /**
270     * Adds a new Structure (group or segment) to this Group. A place for the
271     * Structure is added to the group but there are initially zero repetitions.
272     * This method should be used by the constructors of implementing classes to
273     * specify which Structures the Group contains - Structures should be added
274     * in the order in which they appear. Note that the class is supplied
275     * instead of an instance because we want there initially to be zero
276     * instances of each structure but we want the AbstractGroup code to be able
277     * to create instances as necessary to support get(...) calls.
278     * 
279     * @return the actual name used to store this structure (may be appended
280     *         with an integer if there are duplicates in the same Group).
281     */
282    protected String add(Class<? extends Structure> c, boolean required, boolean repeating, int index) throws HL7Exception {
283        String name = getName(c);
284        return insert(c, required, repeating, index, name);
285    }
286
287    /**
288     * Returns true if the class name is already being used.
289     */
290    private boolean nameExists(String name) {
291        return this.classes.get(name) != null;
292    }
293
294    /**
295     * Attempts to create an instance of the given class and return it as a
296     * Structure.
297     * 
298     * @param c
299     *            the Structure implementing class
300     * @param name
301     *            an optional name of the structure (used by Generic structures;
302     *            may be null)
303     */
304    protected Structure tryToInstantiateStructure(Class<? extends Structure> c, String name) throws HL7Exception {
305        Structure s = null;
306        try {
307            Object o = null;
308            if (GenericSegment.class.isAssignableFrom(c)) {
309                String genericName = name;
310                if (genericName.length() > 3) {
311                        genericName = genericName.substring(0, 3);
312                }
313                
314                                s = new GenericSegment(this, genericName);
315            } else if (GenericGroup.class.isAssignableFrom(c)) {
316                s = new GenericGroup(this, name, myFactory);
317            } else {
318                // first try to instantiate using constructor w/ Message args
319                // ...
320                try {
321                    Class<?>[] argClasses = { Group.class, ModelClassFactory.class };
322                    Object[] argObjects = { this, myFactory };
323                    Constructor<?> con = c.getConstructor(argClasses);
324                    o = con.newInstance(argObjects);
325                } catch (NoSuchMethodException nme) {
326                    o = c.newInstance();
327                }
328                if (!(o instanceof Structure))
329                    throw new HL7Exception("Class " + c.getName() + " does not implement " + "ca.on.uhn.hl7.message.Structure", HL7Exception.APPLICATION_INTERNAL_ERROR);
330                s = (Structure) o;
331            }
332        } catch (Exception e) {
333            if (e instanceof HL7Exception) {
334                throw (HL7Exception) e;
335            } else {
336                throw new HL7Exception("Can't instantiate class " + c.getName(), HL7Exception.APPLICATION_INTERNAL_ERROR, e);
337            }
338        }
339        return s;
340    }
341
342    /**
343     * Returns true if the named structure is a group
344     */
345    public boolean isGroup(String name) throws HL7Exception {
346        Class<? extends Structure> clazz = classes.get(name);
347        if (clazz == null)
348            throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
349        return Group.class.isAssignableFrom(clazz);
350    }
351
352    /**
353     * Returns true if the named structure is required.
354     */
355    public boolean isRequired(String name) throws HL7Exception {
356        Object o = required.get(name);
357        if (o == null)
358            throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
359        Boolean req = (Boolean) o;
360        return req.booleanValue();
361    }
362
363    /**
364     * Returns true if the named structure is required.
365     */
366    public boolean isRepeating(String name) throws HL7Exception {
367        Object o = repeating.get(name);
368        if (o == null)
369            throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
370        Boolean rep = (Boolean) o;
371        return rep.booleanValue();
372    }
373
374    /**
375     * Returns the number of existing repetitions of the named structure.
376     */
377    public int currentReps(String name) throws HL7Exception {
378        List<Structure> list = structures.get(name);
379        if (list == null)
380            throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
381        return list.size();
382    }
383
384    /**
385     * Returns an array of Structure objects by name. For example, if the Group
386     * contains an MSH segment and "MSH" is supplied then this call would return
387     * a 1-element array containing the MSH segment. Multiple elements are
388     * returned when the segment or group repeats. The array may be empty if no
389     * repetitions have been accessed yet using the get(...) methods.
390     * 
391     * @throws HL7Exception
392     *             if the named Structure is not part of this Group.
393     */
394    public Structure[] getAll(String name) throws HL7Exception {
395        List<Structure> list = structures.get(name);
396        if (list == null) {
397            throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
398        }
399        return list.toArray(new Structure[list.size()]);
400    }
401
402    /**
403     * Returns a list containing all existing repetitions of the structure
404     * identified by name
405     * 
406     * @throws HL7Exception
407     *             if the named Structure is not part of this Group.
408     */
409    @SuppressWarnings("unchecked")
410        protected <T extends Structure> List<T> getAllAsList(String name, Class<T> theType) throws HL7Exception {
411        Class<? extends Structure> clazz = classes.get(name);
412        
413                if (!theType.equals(clazz)) {
414                throw new HL7Exception("Structure with name \"" + name + "\" has type " + clazz.getName() + " but should be " + theType); 
415        }
416        List<T> retVal = new ArrayList<T>();
417        for (Structure next : structures.get(name)) {
418                retVal.add((T) next);
419        }
420        return Collections.unmodifiableList(retVal);
421    }
422
423    /**
424     * Removes a repetition of a given Structure objects by name. For example,
425     * if the Group contains 10 repititions an OBX segment and "OBX" is supplied
426     * with an index of 2, then this call would remove the 3rd repetition. Note
427     * that in this case, the Set ID field in the OBX segments would also need
428     * to be renumbered manually.
429     * 
430     * @return The removed structure
431     * @throws HL7Exception
432     *             if the named Structure is not part of this Group.
433     */
434    public Structure removeRepetition(String name, int index) throws HL7Exception {
435        List<Structure> list = structures.get(name);
436        if (list == null) {
437            throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
438        }
439        if (list.size() == 0) {
440            throw new HL7Exception("Invalid index: " + index + ", structure " + name + " has no repetitions", HL7Exception.APPLICATION_INTERNAL_ERROR);
441        }
442        if (list.size() <= index) {
443            throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and " + (list.size() - 1), HL7Exception.APPLICATION_INTERNAL_ERROR);
444        }
445
446        return list.remove(index);
447    }
448
449    /**
450     * Inserts a repetition of a given Structure into repetitions of that
451     * structure by name. For example, if the Group contains 10 repetitions an
452     * OBX segment and an OBX is supplied with an index of 2, then this call
453     * would insert the new repetition at index 2. (Note that in this example, the
454     * Set ID field in the OBX segments would also need to be renumbered
455     * manually).
456     * 
457     * @throws HL7Exception
458     *             if the named Structure is not part of this Group.
459     */
460    protected void insertRepetition(String name, Structure structure, int index) throws HL7Exception {
461        if (structure == null) {
462            throw new NullPointerException("Structure may not be null");
463        }
464
465        if (structure.getMessage() != this.getMessage()) {
466            throw new HL7Exception("Structure does not belong to this message", HL7Exception.APPLICATION_INTERNAL_ERROR);
467        }
468
469        List<Structure> list = structures.get(name);
470
471        if (list == null) {
472            throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
473        }
474        if (list.size() < index) {
475            throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and " + (list.size()), HL7Exception.APPLICATION_INTERNAL_ERROR);
476        }
477
478        list.add(index, structure);
479    }
480
481    /**
482     * Inserts a repetition of a given Structure into repetitions of that
483     * structure by name. For example, if the Group contains 10 repititions an
484     * OBX segment and an OBX is supplied with an index of 2, then this call
485     * would insert the new repetition at index 2. Note that in this case, the
486     * Set ID field in the OBX segments would also need to be renumbered
487     * manually.
488     * 
489     * @return The removed structure
490     * @throws HL7Exception
491     *             if the named Structure is not part of this Group.
492     */
493    public Structure insertRepetition(String name, int index) throws HL7Exception {
494        if (name == null || name.length() == 0) {
495            throw new NullPointerException("Name may not be null/empty");
496        }
497
498        Class<? extends Structure> structureClass = this.classes.get(name);
499        if (structureClass == null) {
500            throw new HL7Exception("Group " + this.getClass().getName() + " has no structure named " + name + ": Valid names: " + this.classes.keySet(),
501                    HL7Exception.APPLICATION_INTERNAL_ERROR);
502        }
503
504        Structure rep = tryToInstantiateStructure(structureClass, name);
505        insertRepetition(name, rep, index);
506
507        return rep;
508    }
509
510    /**
511     * Given a child structure name, returns the child index (which is 1-indexed, meaning
512     * that the first child is at index 1 
513     */
514    public int getFieldNumForName(String name) throws HL7Exception {
515        int retVal = names.indexOf(name);
516        if (retVal == -1) {
517                throw new HL7Exception("Unknown name: " + name);
518        }
519        return retVal + 1;
520    }
521    
522    /**
523     * Returns the Class of the Structure at the given name index.
524     */
525    public Class<? extends Structure> getClass(String name) {
526        return classes.get(name);
527    }
528
529    /**
530     * Returns the class name (excluding package).
531     * 
532     * @see Structure#getName()
533     */
534    public String getName() {
535        return getName(this.getClass());
536    }
537
538    // returns a name for a class of a Structure in this Message
539    private String getName(Class<? extends Structure> c) {
540        String fullName = c.getName();
541        int dotLoc = fullName.lastIndexOf('.');
542        String name = fullName.substring(dotLoc + 1, fullName.length());
543
544        // remove message name prefix from group names for compatibility with
545        // getters ...
546        if (Group.class.isAssignableFrom(c) && !Message.class.isAssignableFrom(c)) {
547            String messageName = getMessage().getName();
548            if (name.startsWith(messageName) && name.length() > messageName.length()) {
549                name = name.substring(messageName.length() + 1);
550            }
551        }
552
553        return name;
554    }
555
556    /**
557     * Inserts the given structure into this group, at the indicated index
558     * number. This method is used to support handling of unexpected segments
559     * (e.g. Z-segments). In contrast, specification of the group's normal
560     * children should be done at construction time, using the add(...) method.
561     */
562    protected String insert(Class<? extends Structure> c, boolean required, boolean repeating, int index, String name) throws HL7Exception {
563        // tryToInstantiateStructure(c, name); //may throw exception
564
565        // see if there is already something by this name and make a new name if
566        // necessary ...
567        if (nameExists(name)) {
568            int version = 2;
569            String newName = name;
570            while (nameExists(newName)) {
571                newName = name + version++;
572            }
573            name = newName;
574        }
575        
576        if (index > this.names.size()) {
577                throw new HL7Exception("Invalid index " + index + " - Should be <= " + this.names.size());
578        }
579        
580        this.names.add(index, name);
581        this.required.put(name, new Boolean(required));
582        this.repeating.put(name, new Boolean(repeating));
583        this.classes.put(name, c);
584        this.structures.put(name, new ArrayList<Structure>());
585
586        return name;
587    }
588
589    /**
590     * Clears all data from this structure.
591     */
592    public void clear() {
593        for (List<Structure> next : structures.values()) {
594            if (next != null) {
595                next.clear();
596            }
597        }
598    }
599
600    /**
601     * Returns the {@link ModelClassFactory} associated with this structure
602     */
603    public final ModelClassFactory getModelClassFactory() {
604        return myFactory;
605    }
606
607    /**
608     * <p>
609     * Appends a description of this group's structure and all children's structure
610     * to a string builder.
611     * </p>
612     * <p>
613     * Note that this method is intended only to be called by {@link AbstractMessage#printStructure()}.
614     * Please use caution if calling this method directly, as the method signature and/or behaviour may 
615     * change in the future.
616     * </p>
617     */
618    void appendStructureDescription(StringBuilder theStringBuilder, int theIndent, boolean theOptional, boolean theRepeating, boolean theAddStartName, boolean theAddEndName) throws HL7Exception {
619        String lineSeparator = System.getProperty("line.separator");
620
621        if (theAddStartName) {
622            indent(theStringBuilder, theIndent);
623            theStringBuilder.append(getName()).append(" (start)").append(lineSeparator);
624        }
625        
626        if (theOptional || theRepeating) {
627            indent(theStringBuilder, theIndent);
628            if (theOptional) {
629                theStringBuilder.append("[");
630            }
631            if (theRepeating) {
632                theStringBuilder.append("{");
633            }
634            theStringBuilder.append(lineSeparator);
635        } 
636
637        for (String nextName : getNames()) {
638            
639            Class<? extends Structure> nextClass = classes.get(nextName);
640            
641            boolean nextOptional = !isRequired(nextName);
642            boolean nextRepeating = isRepeating(nextName);
643            
644            if (AbstractGroup.class.isAssignableFrom(nextClass)) {
645                
646                Structure[] nextChildren = getAll(nextName);
647                for (int i = 0; i < nextChildren.length; i++) {
648                    
649                    Structure structure = nextChildren[i];
650                    boolean addStartName = (i == 0);
651                    boolean addEndName = (i == (nextChildren.length - 1));
652                    ((AbstractGroup)structure).appendStructureDescription(theStringBuilder, theIndent + 3, nextOptional, nextRepeating, addStartName, addEndName);
653                    
654                }
655
656                if (nextChildren.length == 0) {
657                    Structure structure = tryToInstantiateStructure(nextClass, nextName);
658                    ((AbstractGroup)structure).appendStructureDescription(theStringBuilder, theIndent + 3, nextOptional, nextRepeating, true, true);
659                }
660                
661            } else if (Segment.class.isAssignableFrom(nextClass)) {
662                
663                int currentIndent = theStringBuilder.length();
664
665                StringBuilder structurePrefix = new StringBuilder();
666                indent(structurePrefix, theIndent + 3);
667                if (nextOptional) {
668                    structurePrefix.append("[ ");
669                }
670                if (nextRepeating) {
671                    structurePrefix.append("{ ");
672                }
673                structurePrefix.append(nextName);
674                if (nextRepeating) {
675                    structurePrefix.append(" }");
676                }
677                if (nextOptional) {
678                    structurePrefix.append(" ]");
679                }
680
681                if (this.nonStandardNames != null && this.nonStandardNames.contains(nextName)) {
682                    structurePrefix.append(" (non-standard)");
683                }
684                structurePrefix.append(" - ");
685                
686                currentIndent = theStringBuilder.length() - currentIndent;
687                List<Structure> nextStructureList = structures.get(nextName);
688                theStringBuilder.append(structurePrefix);
689                if (nextStructureList == null || nextStructureList.isEmpty()) {
690                    theStringBuilder.append("Not populated");
691                    theStringBuilder.append(lineSeparator);
692                } else {
693                    for (int i = 0; i < nextStructureList.size(); i++) {
694                        if (i > 0) {
695                            indent(theStringBuilder, currentIndent + structurePrefix.length());
696                        }
697                        Segment nextSegment = (Segment)nextStructureList.get(i);
698                        theStringBuilder.append(new PipeParser().doEncode(nextSegment, EncodingCharacters.getInstance(getMessage())));
699                        theStringBuilder.append(lineSeparator);
700                        
701                    }
702                }
703                
704            }
705        }
706
707        if (theOptional || theRepeating) {
708            indent(theStringBuilder, theIndent);
709            if (theRepeating) {
710                theStringBuilder.append("}");
711            }
712            if (theOptional) {
713                theStringBuilder.append("]");
714            }
715            theStringBuilder.append(lineSeparator);
716        } 
717        
718        if (theAddEndName) {
719            indent(theStringBuilder, theIndent);
720            theStringBuilder.append(getName()).append(" (end)").append(lineSeparator);
721        }
722    }
723
724    private void indent(StringBuilder theStringBuilder, int theIndent) {
725        for (int i = 0; i < theIndent; i++) {
726            theStringBuilder.append(' ');
727        }
728    }
729}