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 /** 062 * Do not use 063 */ 064 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"; 065 066 private static final long serialVersionUID = -6686329916234746948L; 067 068 private List<List<Type>> fields; 069 private List<Class<? extends Type>> types; 070 private List<Boolean> required; 071 private List<Integer> length; 072 private List<Object> args; 073 private List<Integer> maxReps; 074 private List<String> names; 075 076 /** 077 * Calls the abstract init() method to create the fields in this segment. 078 * 079 * @param parent 080 * parent group 081 * @param factory 082 * all implementors need a model class factory to find datatype 083 * classes, so we include it as an arg here to emphasize that 084 * fact ... AbstractSegment doesn't actually use it though 085 */ 086 public AbstractSegment(Group parent, ModelClassFactory factory) { 087 super(parent); 088 this.fields = new ArrayList<List<Type>>(); 089 this.types = new ArrayList<Class<? extends Type>>(); 090 this.required = new ArrayList<Boolean>(); 091 this.length = new ArrayList<Integer>(); 092 this.args = new ArrayList<Object>(); 093 this.maxReps = new ArrayList<Integer>(); 094 this.names = new ArrayList<String>(); 095 } 096 097 /** 098 * Returns an array of Field objects at the specified location in the 099 * 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}