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