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