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.HashMap;
033    import java.util.List;
034    
035    import ca.uhn.hl7v2.HL7Exception;
036    import ca.uhn.hl7v2.parser.ModelClassFactory;
037    
038    
039    /**
040     * A partial implementation of Group.  Subclasses correspond to specific
041     * groups of segments (and/or other sub-groups) that are implicitly defined by message structures  
042     * in the HL7 specification.  A subclass should define it's group structure by putting repeated calls to 
043     * the add(...) method in it's constructor.  Each call to add(...) adds a specific component to the 
044     * Group.  
045     * @author Bryan Tripp (bryan_tripp@sourceforge.net)
046     */
047    public abstract class AbstractGroup implements Group {
048        
049        private ArrayList<String> names;
050        private HashMap<String, ArrayList<Structure>> structures;
051        private HashMap<String, Boolean> required;
052        private HashMap<String, Boolean> repeating;
053        private HashMap<String, Class<? extends Structure>> classes;
054        //protected Message message;
055        private Group parent;
056    
057            private final ModelClassFactory myFactory;
058        
059        /** 
060         * This constructor should be used by implementing classes that do not 
061         * also implement Message.
062         *   
063         * @param parent the group to which this Group belongs.
064         * @param factory the factory for classes of segments, groups, and datatypes under this group
065         */
066        protected AbstractGroup(Group parent, ModelClassFactory factory) {
067            this.parent = parent;
068            this.myFactory = factory;
069            init();
070        }
071        
072        /** 
073         * This constructor should only be used by classes that implement Message directly.
074         *  
075         * @param factory the factory for classes of segments, groups, and datatypes under this group
076         */
077        protected AbstractGroup(ModelClassFactory factory) {
078            this.myFactory = factory;
079            init();
080        }
081        
082        private void init() {
083            names = new ArrayList<String>();
084            structures = new HashMap<String, ArrayList<Structure>>();
085            required = new HashMap<String, Boolean>();
086            repeating = new HashMap<String, Boolean>();
087            classes = new HashMap<String, Class<? extends Structure>>();        
088        }
089        
090        /**
091         * Returns the named structure.  If this Structure is repeating then the first 
092         * repetition is returned.  Creates the Structure if necessary.  
093         * @throws HL7Exception if the named Structure is not part of this Group. 
094         */
095        public Structure get(String name) throws HL7Exception {
096            return get(name, 0);
097        }
098    
099        /**
100         * Returns a particular repetition of the named Structure. If the given repetition
101         * number is one greater than the existing number of repetitions then a new  
102         * Structure is created.  
103         * @throws HL7Exception if the named Structure is not part of this group, 
104         *    if the structure is not repeatable and the given rep is > 0,  
105         *    or if the given repetition number is more than one greater than the 
106         *    existing number of repetitions.  
107         */
108        public Structure get(String name,int rep) throws HL7Exception {
109            List<Structure> list = structures.get(name);
110            if (list == null) throw new HL7Exception(name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
111            
112            Structure ret;
113            if (rep < list.size()) {
114                // return existing Structure if it exists 
115                ret = list.get(rep);
116            } else if (rep == list.size()) {
117                //verify that Structure is repeating ... 
118                Boolean repeats = this.repeating.get(name);
119                if (!repeats.booleanValue() && list.size() > 0) throw new HL7Exception("Can't create repetition #" + 
120                    rep + " of Structure " + name + " - this Structure is non-repeating", HL7Exception.APPLICATION_INTERNAL_ERROR);
121                
122                //create a new Structure, add it to the list, and return it
123                Class<? extends Structure> c = classes.get(name); //get class 
124                ret = tryToInstantiateStructure(c, name);
125                list.add(ret);
126            } else {
127                throw new HL7Exception("Can't return repetition #" + rep + " of " + name + 
128                    " - there are only " + list.size() + " repetitions.", HL7Exception.APPLICATION_INTERNAL_ERROR);
129            }
130            return ret;    
131        }
132        
133        /**
134         * Expands the group definition to include a segment that is not 
135         * defined by HL7 to be part of this group (eg an unregistered Z segment). 
136         * The new segment is slotted at the end of the group.  Thenceforward if 
137         * such a segment is encountered it will be parsed into this location. 
138         * If the segment name is unrecognized a GenericSegment is used.  The 
139         * segment is defined as repeating and not required.  
140         */
141        public String addNonstandardSegment(String name) throws HL7Exception {
142            String version = this.getMessage().getVersion();
143            if (version == null) 
144                throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");        
145            Class<? extends Structure> c = myFactory.getSegmentClass(name, version);
146            if (c == null) 
147                c = GenericSegment.class;
148            
149            int index = this.getNames().length;
150            
151            tryToInstantiateStructure(c, name);  //may throw exception
152            
153            return insert(c, false, true, index, name);
154        }
155           
156            public String addNonstandardSegment(String theName, int theIndex) throws HL7Exception {
157            if (this instanceof Message && theIndex == 0) {
158                    throw new HL7Exception("Can not add nonstandard segment \"" + theName + "\" to start of message.");
159            }
160            
161            String version = this.getMessage().getVersion();
162            if (version == null) 
163                throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null");        
164            Class<? extends Structure> c = myFactory.getSegmentClass(theName, version);
165            if (c == null) 
166                c = GenericSegment.class;
167            
168            tryToInstantiateStructure(c, theName);  //may throw exception
169    
170            return insert(c, false, true, theIndex, theName);
171            }
172        
173        /**
174         * Returns an ordered array of the names of the Structures in this 
175         * Group.  These names can be used to iterate through the group using 
176         * repeated calls to <code>get(name)</code>. 
177         */    
178        public String[] getNames() {
179            String[] retVal = new String[this.names.size()];
180            for (int i = 0; i < this.names.size(); i++) {
181                retVal[i] = this.names.get(i);
182            }
183            return retVal;
184        }
185        
186        /**
187         * Adds a new Structure (group or segment) to this Group.  A place for the  
188         * Structure is added to the group but there are initially zero repetitions.  
189         * This method should be used by the constructors of implementing classes 
190         * to specify which Structures the Group contains - Structures should be 
191         * added in the order in which they appear.  
192         * Note that the class is supplied instead of an instance because we want 
193         * there initially to be zero instances of each structure but we want the 
194         * AbstractGroup code to be able to create instances as necessary to support
195         * get(...) calls.
196         * @return the actual name used to store this structure (may be appended with 
197         *      an integer if there are duplicates in the same Group).  
198         */
199        protected String add(Class<? extends Structure> c, boolean required, boolean repeating) throws HL7Exception {
200            String name = getName(c);
201            
202            return insert(c, required, repeating, this.names.size(), name);
203        }
204    
205    
206        /**
207         * Adds a new Structure (group or segment) to this Group.  A place for the
208         * Structure is added to the group but there are initially zero repetitions.
209         * This method should be used by the constructors of implementing classes
210         * to specify which Structures the Group contains - Structures should be
211         * added in the order in which they appear.
212         * Note that the class is supplied instead of an instance because we want
213         * there initially to be zero instances of each structure but we want the
214         * AbstractGroup code to be able to create instances as necessary to support
215         * get(...) calls.
216         * @return the actual name used to store this structure (may be appended with
217         *      an integer if there are duplicates in the same Group).
218         */
219        protected String add(Class<? extends Structure> c, boolean required, boolean repeating, int index) throws HL7Exception {
220            String name = getName(c);
221    
222            return insert(c, required, repeating, index, name);
223        }
224    
225    
226        /** 
227         * Returns true if the class name is already being used. 
228         */
229        private boolean nameExists(String name) {
230            boolean exists = false;
231            Object o = this.classes.get(name);
232            if (o != null) exists = true;
233            return exists;
234        }
235        
236        /**
237         * Attempts to create an instance of the given class and return 
238         * it as a Structure. 
239         * @param c the Structure implementing class
240         * @param name an optional name of the structure (used by Generic structures; may be null)
241         */
242        private Structure tryToInstantiateStructure(Class<? extends Structure> c, String name) throws HL7Exception {
243            Structure s = null;
244            try {
245                Object o = null;
246                if (GenericSegment.class.isAssignableFrom(c)) {
247                    s = new GenericSegment(this, name);
248                } else if (GenericGroup.class.isAssignableFrom(c)) {
249                    s = new GenericGroup(this, name, myFactory);
250                } else {
251                    //first try to instantiate using constructor w/ Message args ...
252                    try {
253                        Class<?>[] argClasses = {Group.class, ModelClassFactory.class};
254                        Object[] argObjects = {this, myFactory};
255                        Constructor<?> con = c.getConstructor(argClasses);
256                        o = con.newInstance(argObjects);
257                    } catch (NoSuchMethodException nme) {
258                        o = c.newInstance();
259                    }
260                    if (!(o instanceof Structure))
261                        throw new HL7Exception("Class " + c.getName() + " does not implement " +
262                        "ca.on.uhn.hl7.message.Structure", HL7Exception.APPLICATION_INTERNAL_ERROR);
263                    s = (Structure)o;
264                }
265            } catch (Exception e) {
266                if (e instanceof HL7Exception) { 
267                    throw (HL7Exception) e;
268                } else {
269                    throw new HL7Exception( 
270                        "Can't instantiate class " + c.getName(), 
271                        HL7Exception.APPLICATION_INTERNAL_ERROR, 
272                        e);
273                }
274            }
275            return s;
276        }
277      
278        /**
279         * Returns the Message to which this segment belongs.
280         */
281        public Message getMessage() {
282            Structure s = this;
283            while (!Message.class.isAssignableFrom(s.getClass())) {
284                s = s.getParent();
285            }
286            return (Message) s;
287        }
288        
289        /** Returns the parent group within which this structure exists (may be root
290         * message group).
291         */
292        public Group getParent() {
293            return this.parent;
294        }
295        
296        /**
297         * Returns true if the named structure is required. 
298         */
299        public boolean isRequired(String name) throws HL7Exception {
300            Object o = required.get(name);
301            if (o == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
302            Boolean req = (Boolean)o;
303            return req.booleanValue();
304        }
305    
306        /**
307         * Returns true if the named structure is required. 
308         */    
309        public boolean isRepeating(String name) throws HL7Exception {
310            Object o = repeating.get(name);
311            if (o == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
312            Boolean rep = (Boolean)o;
313            return rep.booleanValue();
314        }
315        
316        /**
317         * Returns the number of existing repetitions of the named structure.
318         */
319        public int currentReps(String name) throws HL7Exception {
320            ArrayList<Structure> list = structures.get(name);
321            if (list == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
322            return list.size();
323        }
324        
325        /**
326         * Returns an array of Structure objects by name.  For example, if the Group contains
327         * an MSH segment and "MSH" is supplied then this call would return a 1-element array 
328         * containing the MSH segment.  Multiple elements are returned when the segment or 
329         * group repeats.  The array may be empty if no repetitions have been accessed
330         * yet using the get(...) methods. 
331         * @throws HL7Exception if the named Structure is not part of this Group. 
332         */
333        public Structure[] getAll(String name) throws HL7Exception {
334            ArrayList<Structure> list = structures.get(name);
335            if (list == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
336            Structure[] all = new Structure[list.size()];
337            for (int i = 0; i < list.size(); i++) {
338                all[i] = list.get(i);
339            }
340            return all;
341        }
342    
343    
344        /**
345         * Removes a repetition of a given Structure objects by name.  For example, if
346         * the Group contains 10 repititions an OBX segment and "OBX" is supplied
347         * with an index of 2, then this call would remove the 3rd repetition. Note that
348         * in this case, the Set ID field in the OBX segments would also need to be
349         * renumbered manually.
350         *
351         * @return The removed structure
352         * @throws HL7Exception if the named Structure is not part of this Group.
353         */
354        public Structure removeRepetition(String name, int index) throws HL7Exception {
355            ArrayList<Structure> list = structures.get(name);
356            if (list == null) {
357                throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
358            }
359            if (list.size() == 0) {
360                throw new HL7Exception("Invalid index: " + index  + ", structure " + name + " has no repetitions", HL7Exception.APPLICATION_INTERNAL_ERROR);
361            }
362            if (list.size() <= index) {
363                throw new HL7Exception("Invalid index: " + index  + ", structure " + name + " must be between 0 and " + (list.size() - 1), HL7Exception.APPLICATION_INTERNAL_ERROR);
364            }
365    
366            return list.remove(index);
367        }
368    
369    
370        /**
371         * Inserts a repetition of a given Structure into repetitions of that structure by name.
372         * For example, if the Group contains 10 repititions an OBX segment and an OBX is supplied
373         * with an index of 2, then this call would insert the new repetition at
374         * index 2. Note that
375         * in this case, the Set ID field in the OBX segments would also need to be
376         * renumbered manually.
377         *
378         * @return The removed structure
379         * @throws HL7Exception if the named Structure is not part of this Group.
380         */
381        protected void insertRepetition(Structure structure, int index) throws HL7Exception {
382            if (structure == null) {
383                throw new NullPointerException("Structure may not be null");
384            }
385    
386            if (structure.getMessage() != this.getMessage()) {
387                throw new HL7Exception("Structure does not belong to this message", HL7Exception.APPLICATION_INTERNAL_ERROR);
388            }
389    
390            String name = structure.getName();
391            ArrayList<Structure> list = structures.get(name);
392    
393            if (list == null) {
394                throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
395            }
396            if (list.size() < index) {
397                throw new HL7Exception("Invalid index: " + index  + ", structure " + name + " must be between 0 and " + (list.size()), HL7Exception.APPLICATION_INTERNAL_ERROR);
398            }
399    
400            list.add(index, structure);
401        }
402    
403    
404        /**
405         * Inserts a repetition of a given Structure into repetitions of that structure by name.
406         * For example, if the Group contains 10 repititions an OBX segment and an OBX is supplied
407         * with an index of 2, then this call would insert the new repetition at
408         * index 2. Note that
409         * in this case, the Set ID field in the OBX segments would also need to be
410         * renumbered manually.
411         *
412         * @return The removed structure
413         * @throws HL7Exception if the named Structure is not part of this Group.
414         */
415        public Structure insertRepetition(String name, int index) throws HL7Exception {
416            if (name == null || name.length() == 0) {
417                throw new NullPointerException("Name may not be null/empty");
418            }
419    
420            Class<? extends Structure> structureClass = this.classes.get(name);
421            if (structureClass == null) {
422                throw new HL7Exception("Group " + this.getClass().getName() + " has no structure named " + name + ": Valid names: " + this.classes.keySet(), HL7Exception.APPLICATION_INTERNAL_ERROR);
423            }
424    
425            Structure rep = tryToInstantiateStructure(structureClass, name);
426            insertRepetition(rep, index);
427    
428            return rep;
429        }
430    
431    
432        /**
433         * Returns the Class of the Structure at the given name index.  
434         */
435        public Class<? extends Structure> getClass(String name) {
436            return classes.get(name);
437        }
438        
439        /**
440         * Returns the class name (excluding package). 
441         * @see Structure#getName() 
442         */
443        public String getName() {
444            return getName(this.getClass());
445        }
446        
447        //returns a name for a class of a Structure in this Message  
448        private String getName(Class<? extends Structure> c) {
449            String fullName = c.getName();
450            int dotLoc = fullName.lastIndexOf('.');
451            String name = fullName.substring(dotLoc + 1, fullName.length());  
452            
453            //remove message name prefix from group names for compatibility with getters ...
454            if (Group.class.isAssignableFrom(c) && !Message.class.isAssignableFrom(c)) {
455                String messageName = getMessage().getName();
456                if (name.startsWith(messageName) && name.length() > messageName.length()) {
457                    name = name.substring(messageName.length() + 1);
458                }            
459            }
460            
461            return name;        
462        }
463        
464        /**
465         * Inserts the given structure into this group, at the
466         * indicated index number.  This method is used to support handling 
467         * of unexpected segments (e.g. Z-segments).  In contrast, specification 
468         * of the group's normal children should be done at construction time, using the 
469         * add(...) method. 
470         */
471        private String insert(Class<? extends Structure> c, boolean required, boolean repeating, int index, String name) throws HL7Exception {        
472            //tryToInstantiateStructure(c, name);   //may throw exception
473            
474            //see if there is already something by this name and make a new name if necessary ... 
475            if (nameExists(name)) {
476                int version = 2;
477                String newName = name;
478                while (nameExists(newName)) {
479                    newName = name + version++;
480                }
481                name = newName;
482            }   
483            
484            this.names.add(index, name);
485            this.required.put(name, new Boolean(required));
486            this.repeating.put(name, new Boolean(repeating));
487            this.classes.put(name, c);
488            this.structures.put(name, new ArrayList<Structure>());
489            
490            return name;      
491        }
492    
493            /**
494             * Clears all data from this structure.
495             */
496            public void clear() {
497                    for (ArrayList<Structure> next : structures.values()) {
498                            if (next != null) {
499                                    next.clear();
500                            }
501                    }
502            }
503            
504            public final ModelClassFactory getModelClassFactory() {
505                    return myFactory;
506            }
507    
508    }