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 final List<List<Type>> fields; 070 private final List<Class<? extends Type>> types; 071 private final List<Boolean> required; 072 private final List<Integer> length; 073 private final List<Object> args; 074 private final List<Integer> maxReps; 075 private final 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<>(); 090 this.types = new ArrayList<>(); 091 this.required = new ArrayList<>(); 092 this.length = new ArrayList<>(); 093 this.args = new ArrayList<>(); 094 this.maxReps = new ArrayList<>(); 095 this.names = new ArrayList<>(); 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[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}