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