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.Location;
38  import ca.uhn.hl7v2.parser.EncodingCharacters;
39  import ca.uhn.hl7v2.parser.ModelClassFactory;
40  
41  /**
42   * <p>
43   * Provides common functionality needed by implementers of the Segment
44   * interface.
45   * </p>
46   * <p>
47   * Implementing classes should define all the fields for the segment they
48   * represent in their constructor. The add() method is useful for this purpose.
49   * </p>
50   * <p>
51   * For example the constructor for an MSA segment might contain the following
52   * code:<br>
53   * <code>this.add(new ID(), true, 2, null);<br>
54   * this.add(new ST(), true, 20, null);<br>...</code>
55   * </p>
56   * 
57   * @author Bryan Tripp (bryan_tripp@sourceforge.net)
58   */
59  public abstract class AbstractSegment extends AbstractStructure implements
60  		Segment {
61  
62  	/**
63  	 * Do not use
64  	 */
65  	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";
66  
67  	private static final long serialVersionUID = -6686329916234746948L;
68  	
69  	private final List<List<Type>> fields;
70  	private final List<Class<? extends Type>> types;
71  	private final List<Boolean> required;
72  	private final List<Integer> length;
73  	private final List<Object> args;
74  	private final List<Integer> maxReps;
75  	private final List<String> names;
76  
77  	/**
78  	 * Calls the abstract init() method to create the fields in this segment.
79  	 * 
80  	 * @param parent
81  	 *            parent group
82  	 * @param factory
83  	 *            all implementors need a model class factory to find datatype
84  	 *            classes, so we include it as an arg here to emphasize that
85  	 *            fact ... AbstractSegment doesn't actually use it though
86  	 */
87  	public AbstractSegment(Group parent, ModelClassFactory factory) {
88  		super(parent);
89  		this.fields = new ArrayList<>();
90  		this.types = new ArrayList<>();
91  		this.required = new ArrayList<>();
92  		this.length = new ArrayList<>();
93  		this.args = new ArrayList<>();
94  		this.maxReps = new ArrayList<>();
95  		this.names = new ArrayList<>();
96  	}
97  
98      /**
99       * 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                 Fieldld.html#Field">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/../ca/uhn/hl7v2/Location.html#Location">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 }