001    /**
002    The 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. 
004    You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005    Software distributed under the License is distributed on an "AS IS" basis, 
006    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007    specific language governing rights and limitations under the License. 
008    
009    The Original Code is "AbstractGroup.java".  Description: 
010    "A partial implementation of Group" 
011    
012    The Initial Developer of the Original Code is University Health Network. Copyright (C) 
013    2001.  All Rights Reserved. 
014    
015    Contributor(s): ______________________________________. 
016    
017    Alternatively, the contents of this file may be used under the terms of the 
018    GNU General Public License (the  "GPL"), in which case the provisions of the GPL are 
019    applicable instead of those above.  If you wish to allow use of your version of this 
020    file only under the terms of the GPL and not to allow others to use your version 
021    of this file under the MPL, indicate your decision by deleting  the provisions above 
022    and replace  them with the notice and other provisions required by the GPL License.  
023    If you do not delete the provisions above, a recipient may use your version of 
024    this file under either the MPL or the GPL. 
025    
026     */
027    
028    package ca.uhn.hl7v2.model;
029    
030    import java.lang.reflect.Constructor;
031    import java.util.ArrayList;
032    import java.util.Collections;
033    import java.util.HashMap;
034    import java.util.HashSet;
035    import java.util.List;
036    import java.util.Map;
037    import java.util.Set;
038    
039    import ca.uhn.hl7v2.HL7Exception;
040    import ca.uhn.hl7v2.VersionLogger;
041    import ca.uhn.hl7v2.parser.EncodingCharacters;
042    import ca.uhn.hl7v2.parser.ModelClassFactory;
043    import 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     */
054    public 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    }