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.util.ArrayList;
031import java.util.Collections;
032import java.util.HashMap;
033import java.util.HashSet;
034import java.util.List;
035import java.util.Map;
036import java.util.Set;
037
038import ca.uhn.hl7v2.HL7Exception;
039import ca.uhn.hl7v2.VersionLogger;
040import ca.uhn.hl7v2.parser.EncodingCharacters;
041import ca.uhn.hl7v2.parser.ModelClassFactory;
042import ca.uhn.hl7v2.parser.PipeParser;
043import ca.uhn.hl7v2.util.ReflectionUtil;
044
045/**
046 * A partial implementation of Group. Subclasses correspond to specific groups of segments (and/or
047 * other sub-groups) that are implicitly defined by message structures in the HL7 specification. A
048 * subclass should define it's group structure by putting repeated calls to the add(...) method in
049 * it's constructor. Each call to add(...) adds a specific component to the Group.
050 * 
051 * @author Bryan Tripp (bryan_tripp@sourceforge.net)
052 */
053public abstract class AbstractGroup extends AbstractStructure implements Group {
054
055    private static final int PS_INDENT = 3;
056
057        private static final long serialVersionUID = 1772720246448224363L;
058
059    private List<String> names;
060    private Map<String, List<Structure>> structures;
061    private Map<String, Boolean> required;
062    private Map<String, Boolean> repeating;
063    private Set<String> choiceElements;
064    private Map<String, Class<? extends Structure>> classes;
065    // protected Message message;
066    private Set<String> nonStandardNames;
067    private final ModelClassFactory myFactory;
068
069    static {
070        VersionLogger.init();
071    }
072
073    /**
074     * This constructor should be used by implementing classes that do not also implement Message.
075     * 
076     * @param parent the group to which this Group belongs.
077     * @param factory the factory for classes of segments, groups, and datatypes under this group
078     */
079    protected AbstractGroup(Group parent, ModelClassFactory factory) {
080        super(parent);
081        this.myFactory = factory;
082        init();
083    }
084
085    private void init() {
086        names = new ArrayList<String>();
087        structures = new HashMap<String, List<Structure>>();
088        required = new HashMap<String, Boolean>();
089        repeating = new HashMap<String, Boolean>();
090        classes = new HashMap<String, Class<? extends Structure>>();
091        choiceElements = new HashSet<String>();
092    }
093
094    /**
095     * Returns the named structure. If this Structure is repeating then the first repetition is
096     * returned. Creates the Structure if necessary.
097     * 
098     * @throws HL7Exception if the named Structure is not part of this Group.
099     */
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}