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 "AbstractSegment.java".  Description: 
010    "Provides common functionality needed by implementers of the Segment interface.
011      Implementing classes should define all the fields for the segment they represent 
012      in their constructor" 
013    
014    The Initial Developer of the Original Code is University Health Network. Copyright (C) 
015    2001.  All Rights Reserved. 
016    
017    Contributor(s): ______________________________________. 
018    
019    Alternatively, the contents of this file may be used under the terms of the 
020    GNU General Public License (the  ???GPL???), in which case the provisions of the GPL are 
021    applicable instead of those above.  If you wish to allow use of your version of this 
022    file only under the terms of the GPL and not to allow others to use your version 
023    of this file under the MPL, indicate your decision by deleting  the provisions above 
024    and replace  them with the notice and other provisions required by the GPL License.  
025    If you do not delete the provisions above, a recipient may use your version of 
026    this file under either the MPL or the GPL. 
027    
028    */
029    
030    package ca.uhn.hl7v2.model;
031    
032    import java.lang.reflect.InvocationTargetException;
033    import java.util.ArrayList;
034    
035    import ca.uhn.hl7v2.HL7Exception;
036    import ca.uhn.hl7v2.parser.EncodingCharacters;
037    import ca.uhn.hl7v2.parser.ModelClassFactory;
038    import ca.uhn.log.HapiLog;
039    import ca.uhn.log.HapiLogFactory;
040    
041    /**
042     * <p>Provides common functionality needed by implementers of the Segment interface.</p>
043     * <p>Implementing classes should define all the fields for the segment they represent 
044     * in their constructor.  The add() method is useful for this purpose.</p>
045     * <p>For example the constructor for an MSA segment might contain the following code:<br> 
046     * <code>this.add(new ID(), true, 2, null);<br>
047     * this.add(new ST(), true, 20, null);<br>...</code></p>
048     * @author Bryan Tripp (bryan_tripp@sourceforge.net)
049     */
050    public abstract class AbstractSegment implements Segment {
051    
052        private static final long serialVersionUID = -3085455401321160783L;
053    
054        private static final HapiLog log = HapiLogFactory.getHapiLog(AbstractSegment.class);
055    
056        private ArrayList<ArrayList<Type>> fields;
057        private ArrayList<Class<? extends Type>> types;
058        private ArrayList<Boolean> required;
059        private ArrayList<Integer> length;
060        private ArrayList<Object> args;
061        private ArrayList<Integer> maxReps;
062        private ArrayList<String> names;
063        private Group parent;
064        //private Message message;
065        //private String name;
066    
067        /**
068         * Calls the abstract init() method to create the fields in this segment.
069         * 
070         * @param parent parent group
071         * @param factory all implementors need a model class factory to find datatype classes, so we 
072         *      include it as an arg here to emphasize that fact ... AbstractSegment doesn't actually 
073         *      use it though
074         */
075        public AbstractSegment(Group parent, ModelClassFactory factory) {
076            this.parent = parent;
077            this.fields = new ArrayList<ArrayList<Type>>();
078            this.types = new ArrayList<Class<? extends Type>>();
079            this.required = new ArrayList<Boolean>();
080            this.length = new ArrayList<Integer>();
081            this.args = new ArrayList<Object>();
082            this.maxReps = new ArrayList<Integer>();
083            this.names = new ArrayList<String>();
084        }
085    
086        /**
087         * Returns an array of Field objects at the specified location in the segment.  In the case of
088         * non-repeating fields the array will be of length one.  Fields are numbered from 1.
089         */
090        public Type[] getField(int number) throws HL7Exception {
091    
092            ensureEnoughFields(number);
093            
094            if (number < 1 || number > fields.size()) {
095                throw new HL7Exception(
096                    "Can't retrieve field "
097                        + number
098                        + " from segment "
099                        + this.getClass().getName()
100                        + " - there are only "
101                        + fields.size()
102                        + " fields.",
103                    HL7Exception.APPLICATION_INTERNAL_ERROR);
104            }
105    
106                    ArrayList<Type> retVal = fields.get(number - 1);
107            return retVal.toArray(new Type[retVal.size()]); //note: fields are numbered from 1 from the user's perspective
108        }
109    
110        /**
111         * Returns a specific repetition of field at the specified index.  If there exist 
112         * fewer repetitions than are required, the number of repetitions can be increased 
113         * by specifying the lowest repetition that does not yet exist.  For example 
114         * if there are two repetitions but three are needed, the third can be created
115         * and accessed using the following code: <br>
116         * <code>Type t = getField(x, 3);</code>
117         * @param number the field number (starting at 1)
118         * @param rep the repetition number (starting at 0)
119         * @throws HL7Exception if field index is out of range, if the specified 
120         *    repetition is greater than the maximum allowed, or if the specified 
121         *    repetition is more than 1 greater than the existing # of repetitions.  
122         */
123        public Type getField(int number, int rep) throws HL7Exception {
124    
125            ensureEnoughFields(number);
126    
127            if (number < 1 || number > fields.size()) {
128                throw new HL7Exception(
129                    "Can't get field "
130                    + number
131                    + " in segment " + getName() + " - there are currently only "
132                    + fields.size()
133                    + " reps.",
134                    HL7Exception.APPLICATION_INTERNAL_ERROR);
135            }
136    
137            ArrayList<Type> arr = fields.get(number - 1);
138    
139            //check if out of range ... 
140            if (rep > arr.size())
141                throw new HL7Exception(
142                    "Can't get repetition "
143                        + rep
144                        + " from field "
145                        + number
146                        + " - there are currently only "
147                        + arr.size()
148                        + " reps.",
149                    HL7Exception.APPLICATION_INTERNAL_ERROR);
150    
151            /*if (this.getMaxCardinality(number) > 0 && rep >= this.getMaxCardinality(number))
152                throw new HL7Exception(
153                    "Can't get repetition "
154                        + rep
155                        + " from field "
156                        + number
157                        + " - only "
158                        + this.getMaxCardinality(number)
159                        + " reps allowed.",
160                    HL7Exception.APPLICATION_INTERNAL_ERROR);*/
161    
162            //add a rep if necessary ... 
163            if (rep == arr.size()) {
164                Type newType = createNewType(number);
165                            arr.add(newType);
166            }
167    
168            return arr.get(rep);
169        }
170    
171        /**
172         * <p>
173         * Attempts to create an instance of a field type without using reflection.
174         * </p>
175         * <p>
176         * Note that the default implementation just returns <code>null</code>, and it
177         * is not neccesary to override this method to provide any particular behaviour.
178         * When a new field instance is needed within a segment, this method is tried
179         * first, and if it returns <code>null</code>, reflection is used instead.
180         * Implementations of this method is auto-generated by the source generator
181         * module.
182         * </p>
183         * @return Returns a newly instantiated type, or <code>null</code> if not possible
184         * @param field Field number - Note that this is zero indexed!
185         */
186        protected Type createNewTypeWithoutReflection(int field) {
187            return null;
188        }
189    
190        /**
191         * Creates a new instance of the Type at the given field number in this segment.  
192         */
193        private Type createNewType(int field) throws HL7Exception {
194            Type retVal = createNewTypeWithoutReflection(field - 1);
195            if (retVal != null) {
196                    return retVal;
197            }
198            
199            int number = field - 1;
200            Class<? extends Type> c = (Class<? extends Type>) this.types.get(number);
201    
202            Type newType = null;
203            try {
204                Object[] args = getArgs(number);
205                Class<?>[] argClasses = new Class[args.length];
206                for (int i = 0; i < args.length; i++) {
207                    if (args[i] instanceof Message) {
208                        argClasses[i] = Message.class;
209                    } else {
210                        argClasses[i] = args[i].getClass();                        
211                    }
212                }
213                newType = (Type) c.getConstructor(argClasses).newInstance(args);
214            }
215            catch (IllegalAccessException iae) {
216                throw new HL7Exception(
217                    "Can't access class " + c.getName() + " (" + iae.getClass().getName() + "): " + iae.getMessage(),
218                    HL7Exception.APPLICATION_INTERNAL_ERROR);
219            }
220            catch (InstantiationException ie) {
221                throw new HL7Exception(
222                    "Can't instantiate class " + c.getName() + " (" + ie.getClass().getName() + "): " + ie.getMessage(),
223                    HL7Exception.APPLICATION_INTERNAL_ERROR);
224            }
225            catch (InvocationTargetException ite) {
226                throw new HL7Exception(
227                    "Can't instantiate class " + c.getName() + " (" + ite.getClass().getName() + "): " + ite.getMessage(),
228                    HL7Exception.APPLICATION_INTERNAL_ERROR);
229            }
230            catch (NoSuchMethodException nme) {
231                throw new HL7Exception(
232                    "Can't instantiate class " + c.getName() + " (" + nme.getClass().getName() + "): " + nme.getMessage(),
233                    HL7Exception.APPLICATION_INTERNAL_ERROR);
234            }
235            return newType;
236        }
237        
238        //defaults to {this.getMessage}
239        private Object[] getArgs(int fieldNum) {
240            Object[] result = null;
241            
242            Object o = this.args.get(fieldNum);        
243            if (o != null && o instanceof Object[]) {
244                result = (Object[]) o;
245            } else {
246                result = new Object[]{getMessage()};
247            }
248            
249            return result;
250        }
251    
252        /**
253         * Returns true if the given field is required in this segment - fields are 
254         * numbered from 1. 
255         * @throws HL7Exception if field index is out of range.  
256         */
257        public boolean isRequired(int number) throws HL7Exception {
258            if (number < 1 || number > required.size()) {
259                throw new HL7Exception(
260                    "Can't retrieve optionality of field "
261                        + number
262                        + " from segment "
263                        + this.getClass().getName()
264                        + " - there are only "
265                        + fields.size()
266                        + " fields.",
267                    HL7Exception.APPLICATION_INTERNAL_ERROR);
268            }
269    
270            boolean ret = false;
271            try {
272                ret = ((Boolean) required.get(number - 1)).booleanValue();
273            }
274            catch (Exception e) {
275                throw new HL7Exception(
276                    "Can't retrieve optionality of field " + number + ": " + e.getMessage(),
277                    HL7Exception.APPLICATION_INTERNAL_ERROR);
278            }
279    
280            return ret;
281        }
282    
283        /**
284         * Returns the maximum length of the field at the given index, in characters -   
285         * fields are numbered from 1.  
286         * @throws HL7Exception if field index is out of range.  
287         */
288        public int getLength(int number) throws HL7Exception {
289            if (number < 1 || number > length.size()) {
290                throw new HL7Exception(
291                    "Can't retrieve max length of field "
292                        + number
293                        + " from segment "
294                        + this.getClass().getName()
295                        + " - there are only "
296                        + fields.size()
297                        + " fields.",
298                    HL7Exception.APPLICATION_INTERNAL_ERROR);
299            }
300    
301            int ret = 0;
302            try {
303                ret = ((Integer) length.get(number - 1)).intValue(); //fields #d from 1 to user 
304            }
305            catch (Exception e) {
306                throw new HL7Exception(
307                    "Can't retrieve max length of field " + number + ": " + e.getMessage(),
308                    HL7Exception.APPLICATION_INTERNAL_ERROR);
309            }
310    
311            return ret;
312        }
313    
314        /**
315         * Returns the number of repetitions of this field that are allowed.  
316         * @throws HL7Exception if field index is out of range.
317         */
318        public int getMaxCardinality(int number) throws HL7Exception {
319            if (number < 1 || number > length.size()) {
320                throw new HL7Exception(
321                    "Can't retrieve cardinality of field "
322                        + number
323                        + " from segment "
324                        + this.getClass().getName()
325                        + " - there are only "
326                        + fields.size()
327                        + " fields.",
328                    HL7Exception.APPLICATION_INTERNAL_ERROR);
329            }
330    
331            int reps = 0;
332            try {
333                reps = ((Integer) maxReps.get(number - 1)).intValue(); //fields #d from 1 to user
334            }
335            catch (Exception e) {
336                throw new HL7Exception(
337                    "Can't retrieve max repetitions of field " + number + ": " + e.getMessage(),
338                    HL7Exception.APPLICATION_INTERNAL_ERROR);
339            }
340    
341            return reps;
342        }
343    
344        /**
345         * Returns the Message to which this segment belongs.  
346         */
347        public Message getMessage() {
348            Structure s = this;
349            while (!Message.class.isAssignableFrom(s.getClass())) {
350                s = s.getParent();
351            }
352            return (Message) s;
353        }
354        
355        public Group getParent() {
356            return this.parent;
357        }
358    
359        /**
360         * @deprecated Use {@link #add(Class, boolean, int, int, Object[], String)}
361         */
362        protected void add(Class<? extends Type> c, boolean required, int maxReps, int length, Object[] constructorArgs)
363            throws HL7Exception {
364            add(c, required, maxReps, length, constructorArgs, null);
365        }
366    
367        /**
368         * Adds a field to the segment.  The field is initially empty (zero repetitions).   
369         * The field number is sequential depending on previous add() calls.  Implementing 
370         * classes should use the add() method in their constructor in order to define fields 
371         * in their segment.  
372         * @param c the class of the data for this field - this should inherit from Type
373         * @param required whether a value for this field is required in order for the segment 
374         *    to be valid
375         * @param maxReps the maximum number of repetitions - 0 implies that there is no limit
376         * @param length the maximum length of each repetition of the field (in characters) 
377         * @param constructorArgs an array of objects that will be used as constructor arguments 
378         *    if new instances of this class are created (use null for zero-arg constructor)
379         * @param name the name of the field
380         * @throws HL7Exception if the given class does not inherit from Type or if it can 
381         *    not be instantiated.
382         */
383        protected void add(Class<? extends Type> c, boolean required, int maxReps, int length, Object[] constructorArgs, String name)
384            throws HL7Exception {
385            if (!Type.class.isAssignableFrom(c))
386                throw new HL7Exception(
387                    "Class " + c.getName() + " does not inherit from " + "ca.on.uhn.datatype.Type",
388                    HL7Exception.APPLICATION_INTERNAL_ERROR);
389    
390            ArrayList<Type> arr = new ArrayList<Type>();
391            this.types.add(c);
392            this.fields.add(arr);
393            this.required.add(new Boolean(required));
394            this.length.add(new Integer(length));
395            this.args.add(constructorArgs);
396            this.maxReps.add(new Integer(maxReps));
397            this.names.add(name);
398        }
399        
400        /**
401         * Called from getField(...) methods.  If a field has been requested that 
402         * doesn't exist (eg getField(15) when only 10 fields in segment) adds Varies
403         * fields to the end of the segment up to the required number.  
404         */
405        private void ensureEnoughFields(int fieldRequested) {
406            int fieldsToAdd = fieldRequested - this.numFields();
407            if (fieldsToAdd < 0) {
408                fieldsToAdd = 0;
409                    }
410    
411            try {
412                for (int i = 0; i < fieldsToAdd; i++) {
413                    this.add(Varies.class, false, 0, 65536, null); //using 65536 following example of OBX-5
414                }
415            }
416            catch (HL7Exception e) {
417                log.error("Can't create additional generic fields to handle request for field " + fieldRequested, e);
418            }
419        }
420    
421        public static void main(String[] args) {
422            /*
423            try {
424              Message mess = new TestMessage();
425              MSH msh = new MSH(mess);
426              
427              //get empty array 
428              Type[] ts = msh.getField(1);
429              System.out.println("Got Type array of length " + ts.length);
430              
431              //get first field
432              Type t = msh.getField(1, 0);
433              System.out.println("Got a Type of class " + t.getClass().getName());
434              
435              //get array now
436              Type[] ts2 = msh.getField(1);
437              System.out.println("Got Type array of length " + ts2.length);
438              
439              //set a value
440              ST str = (ST)t;
441              str.setValue("hello");
442              
443              //get first field
444              Type t2 = msh.getField(1, 0);
445              System.out.println("Got a Type of class " + t.getClass().getName());
446              System.out.println("It's value is " + ((ST)t2).getValue());
447              
448              msh.getFieldSeparator().setValue("thing");
449              System.out.println("Field Sep: " + msh.getFieldSeparator().getValue());
450              
451              msh.getConformanceStatementID(0).setValue("ID 1");
452              msh.getConformanceStatementID(1).setValue("ID 2");
453              System.out.println("Conf ID #2: " + msh.getConformanceStatementID(1).getValue());
454              
455              ID[] cid = msh.getConformanceStatementID();
456              System.out.println("CID: " + cid);
457              for (int i = 0; i < cid.length; i++) {
458                  System.out.println("Conf ID element: " + i + ": " + cid[i].getValue());
459              }
460              msh.getConformanceStatementID(3).setValue("this should fail");
461              
462              
463            } catch (HL7Exception e) {
464                 e.printStackTrace();
465            }*/
466        }
467    
468        /**
469         * Returns the number of fields defined by this segment (repeating 
470         * fields are not counted multiple times).  
471         */
472        public int numFields() {
473            return this.fields.size();
474        }
475    
476        /**
477         * Returns the class name (excluding package). 
478         * @see Structure#getName() 
479         */
480        public String getName() {
481            String fullName = this.getClass().getName();
482            return fullName.substring(fullName.lastIndexOf('.') + 1, fullName.length());
483        }
484    
485        /**
486         * Sets the segment name.  This would normally be called by a Parser. 
487         */
488        /*public void setName(String name) {
489            this.name = name;
490        }*/
491    
492        /**
493         * {@inheritDoc}
494         */
495        public String[] getNames() {
496                return (String[]) names.toArray(new String[names.size()]);
497        }
498    
499    
500        /**
501         * {@inheritDoc }
502         *
503         * <p><b>Note that this method will not currently work to parse an MSH segment
504         * if the encoding characters are not already set. This limitation should be
505         * resulved in a future version</b></p>
506         */
507        public void parse(String string) throws HL7Exception {
508            EncodingCharacters encodingCharacters = EncodingCharacters.getInstance(getMessage());
509            clear();
510    
511            getMessage().getParser().parse(this, string, encodingCharacters);
512        }
513    
514    
515        /**
516         * {@inheritDoc }
517         */
518        public String encode() throws HL7Exception {
519            return getMessage().getParser().doEncode(this, EncodingCharacters.getInstance(getMessage()));
520        }
521    
522    
523        /**
524         * Removes a repetition of a given field by name.  For example, if
525         * a PID segment contains 10 repititions a "Patient Identifier List" field and "Patient Identifier List" is supplied
526         * with an index of 2, then this call would remove the 3rd repetition.
527         *
528         * @return The removed structure
529         * @throws HL7Exception if the named Structure is not part of this Group.
530         */
531        protected Type removeRepetition(int fieldNum, int index) throws HL7Exception {
532            if (fieldNum < 1 || fieldNum >= fields.size()) {
533                throw new HL7Exception("The field " + fieldNum + " does not exist in the segment " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
534            }
535    
536            String name = names.get(fieldNum - 1);
537            ArrayList<Type> list = fields.get(fieldNum - 1);
538            if (list.size() == 0) {
539                throw new HL7Exception("Invalid index: " + index  + ", structure " + name + " has no repetitions", HL7Exception.APPLICATION_INTERNAL_ERROR);
540            }
541            if (list.size() <= index) {
542                throw new HL7Exception("Invalid index: " + index  + ", structure " + name + " must be between 0 and " + (list.size() - 1), HL7Exception.APPLICATION_INTERNAL_ERROR);
543            }
544    
545            return list.remove(index);
546        }
547    
548    
549        /**
550         * Inserts a repetition of a given Field into repetitions of that field by name.
551         *
552         * @return The newly created and inserted field
553         * @throws HL7Exception if the named Structure is not part of this Group.
554         */
555        protected Type insertRepetition(int fieldNum, int index) throws HL7Exception {
556            if (fieldNum < 1 || fieldNum >= fields.size()) {
557                throw new HL7Exception("The field " + fieldNum + " does not exist in the segment " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR);
558            }
559    
560            ArrayList<Type> list = fields.get(fieldNum - 1);
561            Type newType = createNewType(fieldNum);
562    
563            list.add(index, newType);
564    
565            return newType;
566        }
567    
568    
569        /**
570         * Clears all data from this segment
571         */
572        public void clear() {
573            for (ArrayList<Type> next : fields) {
574                next.clear();
575            }
576        }
577    
578    }