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.Location; 038import ca.uhn.hl7v2.parser.EncodingCharacters; 039import ca.uhn.hl7v2.parser.ModelClassFactory; 040 041/** 042 * <p> 043 * Provides common functionality needed by implementers of the Segment 044 * interface. 045 * </p> 046 * <p> 047 * Implementing classes should define all the fields for the segment they 048 * represent in their constructor. The add() method is useful for this purpose. 049 * </p> 050 * <p> 051 * For example the constructor for an MSA segment might contain the following 052 * code:<br> 053 * <code>this.add(new ID(), true, 2, null);<br> 054 * this.add(new ST(), true, 20, null);<br>...</code> 055 * </p> 056 * 057 * @author Bryan Tripp (bryan_tripp@sourceforge.net) 058 */ 059public abstract class AbstractSegment extends AbstractStructure implements 060 Segment { 061 062 /** 063 * Do not use 064 */ 065 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"; 066 067 private static final long serialVersionUID = -6686329916234746948L; 068 069 private List<List<Type>> fields; 070 private List<Class<? extends Type>> types; 071 private List<Boolean> required; 072 private List<Integer> length; 073 private List<Object> args; 074 private List<Integer> maxReps; 075 private List<String> names; 076 077 /** 078 * Calls the abstract init() method to create the fields in this segment. 079 * 080 * @param parent 081 * parent group 082 * @param factory 083 * all implementors need a model class factory to find datatype 084 * classes, so we include it as an arg here to emphasize that 085 * fact ... AbstractSegment doesn't actually use it though 086 */ 087 public AbstractSegment(Group parent, ModelClassFactory factory) { 088 super(parent); 089 this.fields = new ArrayList<List<Type>>(); 090 this.types = new ArrayList<Class<? extends Type>>(); 091 this.required = new ArrayList<Boolean>(); 092 this.length = new ArrayList<Integer>(); 093 this.args = new ArrayList<Object>(); 094 this.maxReps = new ArrayList<Integer>(); 095 this.names = new ArrayList<String>(); 096 } 097 098 /** 099 * 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 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 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[retVal.size()]); // 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 cce) { 162 log.error("Unexpected problem obtaining field value. This is a bug.", cce); 163 throw new RuntimeException(cce); 164 } catch (HL7Exception he) { 165 log.error("Unexpected problem obtaining field value. This is a bug.", he); 166 throw new RuntimeException(he); 167 } 168 } 169 170 171 protected int getReps(int number) { 172 try { 173 return getFieldAsList(number).size(); 174 } catch (HL7Exception he) { 175 log.error("Unexpected problem obtaining field value. This is a bug.", he); 176 throw new RuntimeException(he); 177 } 178 } 179 180 private List<Type> getFieldAsList(int number) throws HL7Exception { 181 ensureEnoughFields(number); 182 183 if (number < 1 || number > fields.size()) { 184 throw new HL7Exception("Can't retrieve field " + number 185 + " from segment " + this.getClass().getName() 186 + " - there are only " + fields.size() + " fields."); 187 } 188 189 return fields.get(number - 1); 190 191 } 192 193 /** 194 * Returns a specific repetition of field at the specified index. If there 195 * exist fewer repetitions than are required, the number of repetitions can 196 * be increased by specifying the lowest repetition that does not yet exist. 197 * For example if there are two repetitions but three are needed, the third 198 * can be created and accessed using the following code: <br> 199 * <code>Type t = getField(x, 3);</code> 200 * 201 * @param number 202 * the field number (starting at 1) 203 * @param rep 204 * the repetition number (starting at 0) 205 * @throws HL7Exception 206 * if field index is out of range, if the specified repetition 207 * is greater than the maximum allowed, or if the specified 208 * repetition is more than 1 greater than the existing # of 209 * repetitions. 210 */ 211 public Type getField(int number, int rep) throws HL7Exception { 212 213 ensureEnoughFields(number); 214 215 if (number < 1 || number > fields.size()) { 216 throw new HL7Exception("Can't get field " + number + " in segment " 217 + getName() + " - there are currently only " 218 + fields.size() + " reps."); 219 } 220 221 List<Type> arr = fields.get(number - 1); 222 223 // check if out of range ... 224 if (rep > arr.size()) 225 throw new HL7Exception("Can't get repetition " + rep 226 + " from field " + number + " - there are currently only " 227 + arr.size() + " reps."); 228 229 // add a rep if necessary ... 230 if (rep == arr.size()) { 231 Type newType = createNewType(number); 232 arr.add(newType); 233 } 234 235 return arr.get(rep); 236 } 237 238 /** 239 * Returns a specific repetition of field with concrete type at the specified index 240 */ 241 protected <T extends Type> T getTypedField(int number, int rep) { 242 try { 243 @SuppressWarnings("unchecked") T retVal = (T)getField(number, rep); 244 return retVal; 245 } catch (ClassCastException cce) { 246 log.error("Unexpected problem obtaining field value. This is a bug.", cce); 247 throw new RuntimeException(cce); 248 } catch (HL7Exception he) { 249 log.error("Unexpected problem obtaining field value. This is a bug.", he); 250 throw new RuntimeException(he); 251 } 252 } 253 254 /** 255 * <p> 256 * Attempts to create an instance of a field type without using reflection. 257 * </p> 258 * <p> 259 * Note that the default implementation just returns <code>null</code>, and 260 * it is not neccesary to override this method to provide any particular 261 * behaviour. When a new field instance is needed within a segment, this 262 * method is tried first, and if it returns <code>null</code>, reflection is 263 * used instead. Implementations of this method is auto-generated by the 264 * source generator module. 265 * </p> 266 * 267 * @return Returns a newly instantiated type, or <code>null</code> if not 268 * possible 269 * @param field 270 * Field number - Note that this is zero indexed! 271 */ 272 protected Type createNewTypeWithoutReflection(int field) { 273 return null; 274 } 275 276 /** 277 * Creates a new instance of the Type at the given field number in this 278 * segment. 279 */ 280 private Type createNewType(int field) throws HL7Exception { 281 Type retVal = createNewTypeWithoutReflection(field - 1); 282 if (retVal != null) { 283 return retVal; 284 } 285 286 int number = field - 1; 287 Class<? extends Type> c = this.types.get(number); 288 289 Type newType; 290 try { 291 Object[] args = getArgs(number); 292 Class<?>[] argClasses = new Class[args.length]; 293 for (int i = 0; i < args.length; i++) { 294 if (args[i] instanceof Message) { 295 argClasses[i] = Message.class; 296 } else { 297 argClasses[i] = args[i].getClass(); 298 } 299 } 300 newType = c.getConstructor(argClasses).newInstance(args); 301 } catch (IllegalAccessException iae) { 302 throw new HL7Exception("Can't access class " + c.getName() + " (" 303 + iae.getClass().getName() + "): " + iae.getMessage()); 304 } catch (InstantiationException ie) { 305 throw new HL7Exception("Can't instantiate class " + c.getName() 306 + " (" + ie.getClass().getName() + "): " + ie.getMessage()); 307 } catch (InvocationTargetException ite) { 308 throw new HL7Exception("Can't instantiate class " + c.getName() 309 + " (" + ite.getClass().getName() + "): " 310 + ite.getMessage()); 311 } catch (NoSuchMethodException nme) { 312 throw new HL7Exception("Can't instantiate class " + c.getName() 313 + " (" + nme.getClass().getName() + "): " 314 + nme.getMessage()); 315 } 316 return newType; 317 } 318 319 // defaults to {this.getMessage} 320 private Object[] getArgs(int fieldNum) { 321 Object[] result; 322 323 Object o = this.args.get(fieldNum); 324 if (o != null && o instanceof Object[]) { 325 result = (Object[]) o; 326 } else { 327 result = new Object[] { getMessage() }; 328 } 329 330 return result; 331 } 332 333 /** 334 * Returns true if the given field is required in this segment - fields are 335 * numbered from 1. 336 * 337 * @throws HL7Exception 338 * if field index is out of range. 339 */ 340 public boolean isRequired(int number) throws HL7Exception { 341 if (number < 1 || number > required.size()) { 342 throw new HL7Exception("Can't retrieve optionality of field " 343 + number + " from segment " + this.getClass().getName() 344 + " - there are only " + fields.size() + " fields."); 345 } 346 347 try { 348 return required.get(number - 1); 349 } catch (Exception e) { 350 throw new HL7Exception("Can't retrieve optionality of field " 351 + number + ": " + e.getMessage()); 352 } 353 } 354 355 /** 356 * Returns the maximum length of the field at the given index, in characters 357 * - fields are numbered from 1. 358 * 359 * @throws HL7Exception 360 * if field index is out of range. 361 */ 362 public int getLength(int number) throws HL7Exception { 363 if (number < 1 || number > length.size()) { 364 throw new HL7Exception("Can't retrieve max length of field " 365 + number + " from segment " + this.getClass().getName() 366 + " - there are only " + fields.size() + " fields."); 367 } 368 369 try { 370 return length.get(number - 1); // fields #d from 1 to user 371 } catch (Exception e) { 372 throw new HL7Exception("Can't retrieve max length of field " 373 + number + ": " + e.getMessage()); 374 } 375 376 } 377 378 /** 379 * Returns the number of repetitions of this field that are allowed. 380 * 381 * @throws HL7Exception 382 * if field index is out of range. 383 */ 384 public int getMaxCardinality(int number) throws HL7Exception { 385 if (number < 1 || number > length.size()) { 386 throw new HL7Exception("Can't retrieve cardinality of field " 387 + number + " from segment " + this.getClass().getName() 388 + " - there are only " + fields.size() + " fields."); 389 } 390 391 try { 392 return maxReps.get(number - 1); // fields #d from 1 to user 393 } catch (Exception e) { 394 throw new HL7Exception("Can't retrieve max repetitions of field " 395 + number + ": " + e.getMessage()); 396 } 397 } 398 399 /** 400 * @deprecated Use {@link #add(Class, boolean, int, int, Object[], String)} 401 */ 402 protected void add(Class<? extends Type> c, boolean required, int maxReps, 403 int length, Object[] constructorArgs) throws HL7Exception { 404 add(c, required, maxReps, length, constructorArgs, null); 405 } 406 407 /** 408 * Adds a field to the segment. The field is initially empty (zero 409 * repetitions). The field number is sequential depending on previous add() 410 * calls. Implementing classes should use the add() method in their 411 * constructor in order to define fields in their segment. 412 * 413 * @param c 414 * the class of the datatype for the field - this should inherit 415 * from {@link Type} 416 * @param required 417 * whether a value for the field is required in order for the 418 * segment to be valid 419 * @param maxReps 420 * The maximum number of repetitions for the field. Note that 0 implies that there is no 421 * limit, and 1 implies that the field may not repeat. 422 * @param length 423 * the maximum length of each repetition of the field (in 424 * characters) 425 * @param constructorArgs 426 * This parameter provides an array of objects that will be used 427 * as constructor arguments 428 * if new instances of this class are created (use null for 429 * zero-arg constructor). To determine the appropriate value for 430 * this parameter, consult the javadoc for the specific datatype class 431 * passed to the first argument of this method, and provide an array 432 * which satisfies the requirements of its constructor. For example, most 433 * datatypes take a single {@link Message} argument in their constructor. 434 * In that case, the appropriate value for this argument is as follows: 435 * <code>new Object[]{ getMessage() }</code> 436 * @param name 437 * A textual description of the name of the field 438 * @throws HL7Exception 439 * if the given class does not inherit from Type or if it can 440 * not be instantiated. 441 */ 442 protected void add(Class<? extends Type> c, boolean required, int maxReps, 443 int length, Object[] constructorArgs, String name) 444 throws HL7Exception { 445 List<Type> arr = new ArrayList<Type>(); 446 this.types.add(c); 447 this.fields.add(arr); 448 this.required.add(required); 449 this.length.add(length); 450 this.args.add(constructorArgs); 451 this.maxReps.add(maxReps); 452 this.names.add(name); 453 } 454 455 /** 456 * Called from getField(...) methods. If a field has been requested that 457 * doesn't exist (eg getField(15) when only 10 fields in segment) adds 458 * Varies fields to the end of the segment up to the required number. 459 */ 460 private void ensureEnoughFields(int fieldRequested) { 461 int fieldsToAdd = fieldRequested - this.numFields(); 462 if (fieldsToAdd < 0) { 463 fieldsToAdd = 0; 464 } 465 466 try { 467 for (int i = 0; i < fieldsToAdd; i++) { 468 this.add(Varies.class, false, 0, 65536, null); // using 65536 469 // following 470 // example of 471 // OBX-5 472 } 473 } catch (HL7Exception e) { 474 log.error( 475 "Can't create additional generic fields to handle request for field " 476 + fieldRequested, e); 477 } 478 } 479 480 public static void main(String[] args) { 481 /* 482 * try { Message mess = new TestMessage(); MSH msh = new MSH(mess); 483 * 484 * //get empty array Type[] ts = msh.getField(1); 485 * System.out.println("Got Type array of length " + ts.length); 486 * 487 * //get first field Type t = msh.getField(1, 0); 488 * System.out.println("Got a Type of class " + t.getClass().getName()); 489 * 490 * //get array now Type[] ts2 = msh.getField(1); 491 * System.out.println("Got Type array of length " + ts2.length); 492 * 493 * //set a value ST str = (ST)t; str.setValue("hello"); 494 * 495 * //get first field Type t2 = msh.getField(1, 0); 496 * System.out.println("Got a Type of class " + t.getClass().getName()); 497 * System.out.println("It's value is " + ((ST)t2).getValue()); 498 * 499 * msh.getFieldSeparator().setValue("thing"); 500 * System.out.println("Field Sep: " + 501 * msh.getFieldSeparator().getValue()); 502 * 503 * msh.getConformanceStatementID(0).setValue("ID 1"); 504 * msh.getConformanceStatementID(1).setValue("ID 2"); 505 * System.out.println("Conf ID #2: " + 506 * msh.getConformanceStatementID(1).getValue()); 507 * 508 * ID[] cid = msh.getConformanceStatementID(); 509 * System.out.println("CID: " + cid); for (int i = 0; i < cid.length; 510 * i++) { System.out.println("Conf ID element: " + i + ": " + 511 * cid[i].getValue()); } 512 * msh.getConformanceStatementID(3).setValue("this should fail"); 513 * 514 * 515 * } catch (HL7Exception e) { e.printStackTrace(); } 516 */ 517 } 518 519 /** 520 * Returns the number of fields defined by this segment (repeating fields 521 * are not counted multiple times). 522 */ 523 public int numFields() { 524 return this.fields.size(); 525 } 526 527 /** 528 * Returns the class name (excluding package). 529 * 530 * @see Structure#getName() 531 */ 532 public String getName() { 533 String fullName = this.getClass().getName(); 534 return fullName.substring(fullName.lastIndexOf('.') + 1, 535 fullName.length()); 536 } 537 538 /** 539 * Sets the segment name. This would normally be called by a Parser. 540 */ 541 /* 542 * public void setName(String name) { this.name = name; } 543 */ 544 545 /** 546 * {@inheritDoc} 547 */ 548 public String[] getNames() { 549 return names.toArray(new String[names.size()]); 550 } 551 552 /** 553 * {@inheritDoc } 554 * 555 * <p> 556 * <b>Note that this method will not currently work to parse an MSH segment 557 * if the encoding characters are not already set. This limitation should be 558 * resolved in a future version</b> 559 * </p> 560 */ 561 public void parse(String string) throws HL7Exception { 562 if (string == null) { 563 throw new NullPointerException("String can not be null"); 564 } 565 566 EncodingCharacters encodingCharacters; 567 try { 568 encodingCharacters = EncodingCharacters.getInstance(getMessage()); 569 } catch (HL7Exception e) { 570 throw new HL7Exception(ERROR_MSH_1_OR_2_NOT_SET); 571 } 572 clear(); 573 getMessage().getParser().parse(this, string, encodingCharacters); 574 } 575 576 /** 577 * {@inheritDoc } 578 */ 579 public String encode() throws HL7Exception { 580 return getMessage().getParser().doEncode(this, 581 EncodingCharacters.getInstance(getMessage())); 582 } 583 584 /** 585 * Removes a repetition of a given field by name. For example, if a PID 586 * segment contains 10 repetitions a "Patient Identifier List" field and 587 * "Patient Identifier List" is supplied with an index of 2, then this call 588 * would remove the 3rd repetition. 589 * 590 * @return The removed structure 591 * @throws HL7Exception 592 * if the named Structure is not part of this Group. 593 */ 594 public Type removeRepetition(int fieldNum, int index) 595 throws HL7Exception { 596 if (fieldNum < 1 || fieldNum > fields.size()) { 597 throw new HL7Exception("The field " + fieldNum 598 + " does not exist in the segment " 599 + this.getClass().getName()); 600 } 601 602 String name = names.get(fieldNum - 1); 603 List<Type> list = fields.get(fieldNum - 1); 604 if (list.size() == 0) { 605 throw new HL7Exception("Invalid index: " + index + ", structure " 606 + name + " has no repetitions"); 607 } 608 if (list.size() <= index) { 609 throw new HL7Exception("Invalid index: " + index + ", structure " 610 + name + " must be between 0 and " + (list.size() - 1)); 611 } 612 613 return list.remove(index); 614 } 615 616 /** 617 * Inserts a repetition of a given Field into repetitions of that field by 618 * name. 619 * 620 * @return The newly created and inserted field 621 * @throws HL7Exception 622 * if the named Structure is not part of this Group. 623 */ 624 public Type insertRepetition(int fieldNum, int index) 625 throws HL7Exception { 626 if (fieldNum < 1 || fieldNum > fields.size()) { 627 throw new HL7Exception("The field " + fieldNum 628 + " does not exist in the segment " 629 + this.getClass().getName()); 630 } 631 632 List<Type> list = fields.get(fieldNum - 1); 633 Type newType = createNewType(fieldNum); 634 635 list.add(index, newType); 636 637 return newType; 638 } 639 640 /** 641 * Clears all data from this segment 642 */ 643 public void clear() { 644 for (List<Type> next : fields) { 645 next.clear(); 646 } 647 } 648 649}