001 /** 002 The 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. 004 You may obtain a copy of the License at http://www.mozilla.org/MPL/ 005 Software distributed under the License is distributed on an "AS IS" basis, 006 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the 007 specific language governing rights and limitations under the License. 008 009 The 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 014 The Initial Developer of the Original Code is University Health Network. Copyright (C) 015 2001. All Rights Reserved. 016 017 Contributor(s): ______________________________________. 018 019 Alternatively, the contents of this file may be used under the terms of the 020 GNU General Public License (the ???GPL???), in which case the provisions of the GPL are 021 applicable instead of those above. If you wish to allow use of your version of this 022 file only under the terms of the GPL and not to allow others to use your version 023 of this file under the MPL, indicate your decision by deleting the provisions above 024 and replace them with the notice and other provisions required by the GPL License. 025 If you do not delete the provisions above, a recipient may use your version of 026 this file under either the MPL or the GPL. 027 028 */ 029 030 package ca.uhn.hl7v2.model; 031 032 import java.lang.reflect.InvocationTargetException; 033 import java.util.ArrayList; 034 035 import ca.uhn.hl7v2.HL7Exception; 036 import ca.uhn.hl7v2.parser.EncodingCharacters; 037 import ca.uhn.hl7v2.parser.ModelClassFactory; 038 import ca.uhn.log.HapiLog; 039 import ca.uhn.log.HapiLogFactory; 040 041 /** 042 * <p>Provides common functionality needed by implementers of the Segment interface.</p> 043 * <p>Implementing classes should define all the fields for the segment they represent 044 * in their constructor. The add() method is useful for this purpose.</p> 045 * <p>For example the constructor for an MSA segment might contain the following code:<br> 046 * <code>this.add(new ID(), true, 2, null);<br> 047 * this.add(new ST(), true, 20, null);<br>...</code></p> 048 * @author Bryan Tripp (bryan_tripp@sourceforge.net) 049 */ 050 public abstract class AbstractSegment implements Segment { 051 052 private static final long serialVersionUID = -3085455401321160783L; 053 054 private static final HapiLog log = HapiLogFactory.getHapiLog(AbstractSegment.class); 055 056 private ArrayList<ArrayList<Type>> fields; 057 private ArrayList<Class<? extends Type>> types; 058 private ArrayList<Boolean> required; 059 private ArrayList<Integer> length; 060 private ArrayList<Object> args; 061 private ArrayList<Integer> maxReps; 062 private ArrayList<String> names; 063 private Group parent; 064 //private Message message; 065 //private String name; 066 067 /** 068 * Calls the abstract init() method to create the fields in this segment. 069 * 070 * @param parent parent group 071 * @param factory all implementors need a model class factory to find datatype classes, so we 072 * include it as an arg here to emphasize that fact ... AbstractSegment doesn't actually 073 * use it though 074 */ 075 public AbstractSegment(Group parent, ModelClassFactory factory) { 076 this.parent = parent; 077 this.fields = new ArrayList<ArrayList<Type>>(); 078 this.types = new ArrayList<Class<? extends Type>>(); 079 this.required = new ArrayList<Boolean>(); 080 this.length = new ArrayList<Integer>(); 081 this.args = new ArrayList<Object>(); 082 this.maxReps = new ArrayList<Integer>(); 083 this.names = new ArrayList<String>(); 084 } 085 086 /** 087 * Returns an array of Field objects at the specified location in the segment. In the case of 088 * non-repeating fields the array will be of length one. Fields are numbered from 1. 089 */ 090 public Type[] getField(int number) throws HL7Exception { 091 092 ensureEnoughFields(number); 093 094 if (number < 1 || number > fields.size()) { 095 throw new HL7Exception( 096 "Can't retrieve field " 097 + number 098 + " from segment " 099 + this.getClass().getName() 100 + " - there are only " 101 + fields.size() 102 + " fields.", 103 HL7Exception.APPLICATION_INTERNAL_ERROR); 104 } 105 106 ArrayList<Type> retVal = fields.get(number - 1); 107 return retVal.toArray(new Type[retVal.size()]); //note: fields are numbered from 1 from the user's perspective 108 } 109 110 /** 111 * Returns a specific repetition of field at the specified index. If there exist 112 * fewer repetitions than are required, the number of repetitions can be increased 113 * by specifying the lowest repetition that does not yet exist. For example 114 * if there are two repetitions but three are needed, the third can be created 115 * and accessed using the following code: <br> 116 * <code>Type t = getField(x, 3);</code> 117 * @param number the field number (starting at 1) 118 * @param rep the repetition number (starting at 0) 119 * @throws HL7Exception if field index is out of range, if the specified 120 * repetition is greater than the maximum allowed, or if the specified 121 * repetition is more than 1 greater than the existing # of repetitions. 122 */ 123 public Type getField(int number, int rep) throws HL7Exception { 124 125 ensureEnoughFields(number); 126 127 if (number < 1 || number > fields.size()) { 128 throw new HL7Exception( 129 "Can't get field " 130 + number 131 + " in segment " + getName() + " - there are currently only " 132 + fields.size() 133 + " reps.", 134 HL7Exception.APPLICATION_INTERNAL_ERROR); 135 } 136 137 ArrayList<Type> arr = fields.get(number - 1); 138 139 //check if out of range ... 140 if (rep > arr.size()) 141 throw new HL7Exception( 142 "Can't get repetition " 143 + rep 144 + " from field " 145 + number 146 + " - there are currently only " 147 + arr.size() 148 + " reps.", 149 HL7Exception.APPLICATION_INTERNAL_ERROR); 150 151 /*if (this.getMaxCardinality(number) > 0 && rep >= this.getMaxCardinality(number)) 152 throw new HL7Exception( 153 "Can't get repetition " 154 + rep 155 + " from field " 156 + number 157 + " - only " 158 + this.getMaxCardinality(number) 159 + " reps allowed.", 160 HL7Exception.APPLICATION_INTERNAL_ERROR);*/ 161 162 //add a rep if necessary ... 163 if (rep == arr.size()) { 164 Type newType = createNewType(number); 165 arr.add(newType); 166 } 167 168 return arr.get(rep); 169 } 170 171 /** 172 * <p> 173 * Attempts to create an instance of a field type without using reflection. 174 * </p> 175 * <p> 176 * Note that the default implementation just returns <code>null</code>, and it 177 * is not neccesary to override this method to provide any particular behaviour. 178 * When a new field instance is needed within a segment, this method is tried 179 * first, and if it returns <code>null</code>, reflection is used instead. 180 * Implementations of this method is auto-generated by the source generator 181 * module. 182 * </p> 183 * @return Returns a newly instantiated type, or <code>null</code> if not possible 184 * @param field Field number - Note that this is zero indexed! 185 */ 186 protected Type createNewTypeWithoutReflection(int field) { 187 return null; 188 } 189 190 /** 191 * Creates a new instance of the Type at the given field number in this segment. 192 */ 193 private Type createNewType(int field) throws HL7Exception { 194 Type retVal = createNewTypeWithoutReflection(field - 1); 195 if (retVal != null) { 196 return retVal; 197 } 198 199 int number = field - 1; 200 Class<? extends Type> c = (Class<? extends Type>) this.types.get(number); 201 202 Type newType = null; 203 try { 204 Object[] args = getArgs(number); 205 Class<?>[] argClasses = new Class[args.length]; 206 for (int i = 0; i < args.length; i++) { 207 if (args[i] instanceof Message) { 208 argClasses[i] = Message.class; 209 } else { 210 argClasses[i] = args[i].getClass(); 211 } 212 } 213 newType = (Type) c.getConstructor(argClasses).newInstance(args); 214 } 215 catch (IllegalAccessException iae) { 216 throw new HL7Exception( 217 "Can't access class " + c.getName() + " (" + iae.getClass().getName() + "): " + iae.getMessage(), 218 HL7Exception.APPLICATION_INTERNAL_ERROR); 219 } 220 catch (InstantiationException ie) { 221 throw new HL7Exception( 222 "Can't instantiate class " + c.getName() + " (" + ie.getClass().getName() + "): " + ie.getMessage(), 223 HL7Exception.APPLICATION_INTERNAL_ERROR); 224 } 225 catch (InvocationTargetException ite) { 226 throw new HL7Exception( 227 "Can't instantiate class " + c.getName() + " (" + ite.getClass().getName() + "): " + ite.getMessage(), 228 HL7Exception.APPLICATION_INTERNAL_ERROR); 229 } 230 catch (NoSuchMethodException nme) { 231 throw new HL7Exception( 232 "Can't instantiate class " + c.getName() + " (" + nme.getClass().getName() + "): " + nme.getMessage(), 233 HL7Exception.APPLICATION_INTERNAL_ERROR); 234 } 235 return newType; 236 } 237 238 //defaults to {this.getMessage} 239 private Object[] getArgs(int fieldNum) { 240 Object[] result = null; 241 242 Object o = this.args.get(fieldNum); 243 if (o != null && o instanceof Object[]) { 244 result = (Object[]) o; 245 } else { 246 result = new Object[]{getMessage()}; 247 } 248 249 return result; 250 } 251 252 /** 253 * Returns true if the given field is required in this segment - fields are 254 * numbered from 1. 255 * @throws HL7Exception if field index is out of range. 256 */ 257 public boolean isRequired(int number) throws HL7Exception { 258 if (number < 1 || number > required.size()) { 259 throw new HL7Exception( 260 "Can't retrieve optionality of field " 261 + number 262 + " from segment " 263 + this.getClass().getName() 264 + " - there are only " 265 + fields.size() 266 + " fields.", 267 HL7Exception.APPLICATION_INTERNAL_ERROR); 268 } 269 270 boolean ret = false; 271 try { 272 ret = ((Boolean) required.get(number - 1)).booleanValue(); 273 } 274 catch (Exception e) { 275 throw new HL7Exception( 276 "Can't retrieve optionality of field " + number + ": " + e.getMessage(), 277 HL7Exception.APPLICATION_INTERNAL_ERROR); 278 } 279 280 return ret; 281 } 282 283 /** 284 * Returns the maximum length of the field at the given index, in characters - 285 * fields are numbered from 1. 286 * @throws HL7Exception if field index is out of range. 287 */ 288 public int getLength(int number) throws HL7Exception { 289 if (number < 1 || number > length.size()) { 290 throw new HL7Exception( 291 "Can't retrieve max length of field " 292 + number 293 + " from segment " 294 + this.getClass().getName() 295 + " - there are only " 296 + fields.size() 297 + " fields.", 298 HL7Exception.APPLICATION_INTERNAL_ERROR); 299 } 300 301 int ret = 0; 302 try { 303 ret = ((Integer) length.get(number - 1)).intValue(); //fields #d from 1 to user 304 } 305 catch (Exception e) { 306 throw new HL7Exception( 307 "Can't retrieve max length of field " + number + ": " + e.getMessage(), 308 HL7Exception.APPLICATION_INTERNAL_ERROR); 309 } 310 311 return ret; 312 } 313 314 /** 315 * Returns the number of repetitions of this field that are allowed. 316 * @throws HL7Exception if field index is out of range. 317 */ 318 public int getMaxCardinality(int number) throws HL7Exception { 319 if (number < 1 || number > length.size()) { 320 throw new HL7Exception( 321 "Can't retrieve cardinality of field " 322 + number 323 + " from segment " 324 + this.getClass().getName() 325 + " - there are only " 326 + fields.size() 327 + " fields.", 328 HL7Exception.APPLICATION_INTERNAL_ERROR); 329 } 330 331 int reps = 0; 332 try { 333 reps = ((Integer) maxReps.get(number - 1)).intValue(); //fields #d from 1 to user 334 } 335 catch (Exception e) { 336 throw new HL7Exception( 337 "Can't retrieve max repetitions of field " + number + ": " + e.getMessage(), 338 HL7Exception.APPLICATION_INTERNAL_ERROR); 339 } 340 341 return reps; 342 } 343 344 /** 345 * Returns the Message to which this segment belongs. 346 */ 347 public Message getMessage() { 348 Structure s = this; 349 while (!Message.class.isAssignableFrom(s.getClass())) { 350 s = s.getParent(); 351 } 352 return (Message) s; 353 } 354 355 public Group getParent() { 356 return this.parent; 357 } 358 359 /** 360 * @deprecated Use {@link #add(Class, boolean, int, int, Object[], String)} 361 */ 362 protected void add(Class<? extends Type> c, boolean required, int maxReps, int length, Object[] constructorArgs) 363 throws HL7Exception { 364 add(c, required, maxReps, length, constructorArgs, null); 365 } 366 367 /** 368 * Adds a field to the segment. The field is initially empty (zero repetitions). 369 * The field number is sequential depending on previous add() calls. Implementing 370 * classes should use the add() method in their constructor in order to define fields 371 * in their segment. 372 * @param c the class of the data for this field - this should inherit from Type 373 * @param required whether a value for this field is required in order for the segment 374 * to be valid 375 * @param maxReps the maximum number of repetitions - 0 implies that there is no limit 376 * @param length the maximum length of each repetition of the field (in characters) 377 * @param constructorArgs an array of objects that will be used as constructor arguments 378 * if new instances of this class are created (use null for zero-arg constructor) 379 * @param name the name of the field 380 * @throws HL7Exception if the given class does not inherit from Type or if it can 381 * not be instantiated. 382 */ 383 protected void add(Class<? extends Type> c, boolean required, int maxReps, int length, Object[] constructorArgs, String name) 384 throws HL7Exception { 385 if (!Type.class.isAssignableFrom(c)) 386 throw new HL7Exception( 387 "Class " + c.getName() + " does not inherit from " + "ca.on.uhn.datatype.Type", 388 HL7Exception.APPLICATION_INTERNAL_ERROR); 389 390 ArrayList<Type> arr = new ArrayList<Type>(); 391 this.types.add(c); 392 this.fields.add(arr); 393 this.required.add(new Boolean(required)); 394 this.length.add(new Integer(length)); 395 this.args.add(constructorArgs); 396 this.maxReps.add(new Integer(maxReps)); 397 this.names.add(name); 398 } 399 400 /** 401 * Called from getField(...) methods. If a field has been requested that 402 * doesn't exist (eg getField(15) when only 10 fields in segment) adds Varies 403 * fields to the end of the segment up to the required number. 404 */ 405 private void ensureEnoughFields(int fieldRequested) { 406 int fieldsToAdd = fieldRequested - this.numFields(); 407 if (fieldsToAdd < 0) { 408 fieldsToAdd = 0; 409 } 410 411 try { 412 for (int i = 0; i < fieldsToAdd; i++) { 413 this.add(Varies.class, false, 0, 65536, null); //using 65536 following example of OBX-5 414 } 415 } 416 catch (HL7Exception e) { 417 log.error("Can't create additional generic fields to handle request for field " + fieldRequested, e); 418 } 419 } 420 421 public static void main(String[] args) { 422 /* 423 try { 424 Message mess = new TestMessage(); 425 MSH msh = new MSH(mess); 426 427 //get empty array 428 Type[] ts = msh.getField(1); 429 System.out.println("Got Type array of length " + ts.length); 430 431 //get first field 432 Type t = msh.getField(1, 0); 433 System.out.println("Got a Type of class " + t.getClass().getName()); 434 435 //get array now 436 Type[] ts2 = msh.getField(1); 437 System.out.println("Got Type array of length " + ts2.length); 438 439 //set a value 440 ST str = (ST)t; 441 str.setValue("hello"); 442 443 //get first field 444 Type t2 = msh.getField(1, 0); 445 System.out.println("Got a Type of class " + t.getClass().getName()); 446 System.out.println("It's value is " + ((ST)t2).getValue()); 447 448 msh.getFieldSeparator().setValue("thing"); 449 System.out.println("Field Sep: " + msh.getFieldSeparator().getValue()); 450 451 msh.getConformanceStatementID(0).setValue("ID 1"); 452 msh.getConformanceStatementID(1).setValue("ID 2"); 453 System.out.println("Conf ID #2: " + msh.getConformanceStatementID(1).getValue()); 454 455 ID[] cid = msh.getConformanceStatementID(); 456 System.out.println("CID: " + cid); 457 for (int i = 0; i < cid.length; i++) { 458 System.out.println("Conf ID element: " + i + ": " + cid[i].getValue()); 459 } 460 msh.getConformanceStatementID(3).setValue("this should fail"); 461 462 463 } catch (HL7Exception e) { 464 e.printStackTrace(); 465 }*/ 466 } 467 468 /** 469 * Returns the number of fields defined by this segment (repeating 470 * fields are not counted multiple times). 471 */ 472 public int numFields() { 473 return this.fields.size(); 474 } 475 476 /** 477 * Returns the class name (excluding package). 478 * @see Structure#getName() 479 */ 480 public String getName() { 481 String fullName = this.getClass().getName(); 482 return fullName.substring(fullName.lastIndexOf('.') + 1, fullName.length()); 483 } 484 485 /** 486 * Sets the segment name. This would normally be called by a Parser. 487 */ 488 /*public void setName(String name) { 489 this.name = name; 490 }*/ 491 492 /** 493 * {@inheritDoc} 494 */ 495 public String[] getNames() { 496 return (String[]) names.toArray(new String[names.size()]); 497 } 498 499 500 /** 501 * {@inheritDoc } 502 * 503 * <p><b>Note that this method will not currently work to parse an MSH segment 504 * if the encoding characters are not already set. This limitation should be 505 * resulved in a future version</b></p> 506 */ 507 public void parse(String string) throws HL7Exception { 508 EncodingCharacters encodingCharacters = EncodingCharacters.getInstance(getMessage()); 509 clear(); 510 511 getMessage().getParser().parse(this, string, encodingCharacters); 512 } 513 514 515 /** 516 * {@inheritDoc } 517 */ 518 public String encode() throws HL7Exception { 519 return getMessage().getParser().doEncode(this, EncodingCharacters.getInstance(getMessage())); 520 } 521 522 523 /** 524 * Removes a repetition of a given field by name. For example, if 525 * a PID segment contains 10 repititions a "Patient Identifier List" field and "Patient Identifier List" is supplied 526 * with an index of 2, then this call would remove the 3rd repetition. 527 * 528 * @return The removed structure 529 * @throws HL7Exception if the named Structure is not part of this Group. 530 */ 531 protected Type removeRepetition(int fieldNum, int index) throws HL7Exception { 532 if (fieldNum < 1 || fieldNum >= fields.size()) { 533 throw new HL7Exception("The field " + fieldNum + " does not exist in the segment " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 534 } 535 536 String name = names.get(fieldNum - 1); 537 ArrayList<Type> list = fields.get(fieldNum - 1); 538 if (list.size() == 0) { 539 throw new HL7Exception("Invalid index: " + index + ", structure " + name + " has no repetitions", HL7Exception.APPLICATION_INTERNAL_ERROR); 540 } 541 if (list.size() <= index) { 542 throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and " + (list.size() - 1), HL7Exception.APPLICATION_INTERNAL_ERROR); 543 } 544 545 return list.remove(index); 546 } 547 548 549 /** 550 * Inserts a repetition of a given Field into repetitions of that field by name. 551 * 552 * @return The newly created and inserted field 553 * @throws HL7Exception if the named Structure is not part of this Group. 554 */ 555 protected Type insertRepetition(int fieldNum, int index) throws HL7Exception { 556 if (fieldNum < 1 || fieldNum >= fields.size()) { 557 throw new HL7Exception("The field " + fieldNum + " does not exist in the segment " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 558 } 559 560 ArrayList<Type> list = fields.get(fieldNum - 1); 561 Type newType = createNewType(fieldNum); 562 563 list.add(index, newType); 564 565 return newType; 566 } 567 568 569 /** 570 * Clears all data from this segment 571 */ 572 public void clear() { 573 for (ArrayList<Type> next : fields) { 574 next.clear(); 575 } 576 } 577 578 }