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.parser.EncodingCharacters; 038import 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 */ 058public 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}