View Javadoc

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