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