001    /**
002    The contents of this file are subject to the Mozilla Public License Version 1.1 
003    (the "License"); you may not use this file except in compliance with the License. 
004    You may obtain a copy of the License at http://www.mozilla.org/MPL/ 
005    Software distributed under the License is distributed on an "AS IS" basis, 
006    WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 
007    specific language governing rights and limitations under the License. 
008    
009    The Original Code is "AbstractSegment.java".  Description: 
010    "Provides common functionality needed by implementers of the Segment interface.
011      Implementing classes should define all the fields for the segment they represent 
012      in their constructor" 
013    
014    The Initial Developer of the Original Code is University Health Network. Copyright (C) 
015    2001.  All Rights Reserved. 
016    
017    Contributor(s): ______________________________________. 
018    
019    Alternatively, the contents of this file may be used under the terms of the 
020    GNU General Public License (the  �GPL�), in which case the provisions of the GPL are 
021    applicable instead of those above.  If you wish to allow use of your version of this 
022    file only under the terms of the GPL and not to allow others to use your version 
023    of this file under the MPL, indicate your decision by deleting  the provisions above 
024    and replace  them with the notice and other provisions required by the GPL License.  
025    If you do not delete the provisions above, a recipient may use your version of 
026    this file under either the MPL or the GPL. 
027    
028     */
029    
030    package ca.uhn.hl7v2.model;
031    
032    import java.lang.reflect.InvocationTargetException;
033    import java.util.ArrayList;
034    import java.util.List;
035    
036    import ca.uhn.hl7v2.HL7Exception;
037    import ca.uhn.hl7v2.parser.EncodingCharacters;
038    import 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     */
058    public 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    }