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