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 "AbstractGroup.java". Description: 010 "A partial implementation of Group" 011 012 The Initial Developer of the Original Code is University Health Network. Copyright (C) 013 2001. All Rights Reserved. 014 015 Contributor(s): ______________________________________. 016 017 Alternatively, the contents of this file may be used under the terms of the 018 GNU General Public License (the ???GPL???), in which case the provisions of the GPL are 019 applicable instead of those above. If you wish to allow use of your version of this 020 file only under the terms of the GPL and not to allow others to use your version 021 of this file under the MPL, indicate your decision by deleting the provisions above 022 and replace them with the notice and other provisions required by the GPL License. 023 If you do not delete the provisions above, a recipient may use your version of 024 this file under either the MPL or the GPL. 025 026 */ 027 028 package ca.uhn.hl7v2.model; 029 030 import java.lang.reflect.Constructor; 031 import java.util.ArrayList; 032 import java.util.HashMap; 033 import java.util.List; 034 035 import ca.uhn.hl7v2.HL7Exception; 036 import ca.uhn.hl7v2.parser.ModelClassFactory; 037 038 039 /** 040 * A partial implementation of Group. Subclasses correspond to specific 041 * groups of segments (and/or other sub-groups) that are implicitly defined by message structures 042 * in the HL7 specification. A subclass should define it's group structure by putting repeated calls to 043 * the add(...) method in it's constructor. Each call to add(...) adds a specific component to the 044 * Group. 045 * @author Bryan Tripp (bryan_tripp@sourceforge.net) 046 */ 047 public abstract class AbstractGroup implements Group { 048 049 private ArrayList<String> names; 050 private HashMap<String, ArrayList<Structure>> structures; 051 private HashMap<String, Boolean> required; 052 private HashMap<String, Boolean> repeating; 053 private HashMap<String, Class<? extends Structure>> classes; 054 //protected Message message; 055 private Group parent; 056 057 private final ModelClassFactory myFactory; 058 059 /** 060 * This constructor should be used by implementing classes that do not 061 * also implement Message. 062 * 063 * @param parent the group to which this Group belongs. 064 * @param factory the factory for classes of segments, groups, and datatypes under this group 065 */ 066 protected AbstractGroup(Group parent, ModelClassFactory factory) { 067 this.parent = parent; 068 this.myFactory = factory; 069 init(); 070 } 071 072 /** 073 * This constructor should only be used by classes that implement Message directly. 074 * 075 * @param factory the factory for classes of segments, groups, and datatypes under this group 076 */ 077 protected AbstractGroup(ModelClassFactory factory) { 078 this.myFactory = factory; 079 init(); 080 } 081 082 private void init() { 083 names = new ArrayList<String>(); 084 structures = new HashMap<String, ArrayList<Structure>>(); 085 required = new HashMap<String, Boolean>(); 086 repeating = new HashMap<String, Boolean>(); 087 classes = new HashMap<String, Class<? extends Structure>>(); 088 } 089 090 /** 091 * Returns the named structure. If this Structure is repeating then the first 092 * repetition is returned. Creates the Structure if necessary. 093 * @throws HL7Exception if the named Structure is not part of this Group. 094 */ 095 public Structure get(String name) throws HL7Exception { 096 return get(name, 0); 097 } 098 099 /** 100 * Returns a particular repetition of the named Structure. If the given repetition 101 * number is one greater than the existing number of repetitions then a new 102 * Structure is created. 103 * @throws HL7Exception if the named Structure is not part of this group, 104 * if the structure is not repeatable and the given rep is > 0, 105 * or if the given repetition number is more than one greater than the 106 * existing number of repetitions. 107 */ 108 public Structure get(String name,int rep) throws HL7Exception { 109 List<Structure> list = structures.get(name); 110 if (list == null) throw new HL7Exception(name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 111 112 Structure ret; 113 if (rep < list.size()) { 114 // return existing Structure if it exists 115 ret = list.get(rep); 116 } else if (rep == list.size()) { 117 //verify that Structure is repeating ... 118 Boolean repeats = this.repeating.get(name); 119 if (!repeats.booleanValue() && list.size() > 0) throw new HL7Exception("Can't create repetition #" + 120 rep + " of Structure " + name + " - this Structure is non-repeating", HL7Exception.APPLICATION_INTERNAL_ERROR); 121 122 //create a new Structure, add it to the list, and return it 123 Class<? extends Structure> c = classes.get(name); //get class 124 ret = tryToInstantiateStructure(c, name); 125 list.add(ret); 126 } else { 127 throw new HL7Exception("Can't return repetition #" + rep + " of " + name + 128 " - there are only " + list.size() + " repetitions.", HL7Exception.APPLICATION_INTERNAL_ERROR); 129 } 130 return ret; 131 } 132 133 /** 134 * Expands the group definition to include a segment that is not 135 * defined by HL7 to be part of this group (eg an unregistered Z segment). 136 * The new segment is slotted at the end of the group. Thenceforward if 137 * such a segment is encountered it will be parsed into this location. 138 * If the segment name is unrecognized a GenericSegment is used. The 139 * segment is defined as repeating and not required. 140 */ 141 public String addNonstandardSegment(String name) throws HL7Exception { 142 String version = this.getMessage().getVersion(); 143 if (version == null) 144 throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null"); 145 Class<? extends Structure> c = myFactory.getSegmentClass(name, version); 146 if (c == null) 147 c = GenericSegment.class; 148 149 int index = this.getNames().length; 150 151 tryToInstantiateStructure(c, name); //may throw exception 152 153 return insert(c, false, true, index, name); 154 } 155 156 public String addNonstandardSegment(String theName, int theIndex) throws HL7Exception { 157 if (this instanceof Message && theIndex == 0) { 158 throw new HL7Exception("Can not add nonstandard segment \"" + theName + "\" to start of message."); 159 } 160 161 String version = this.getMessage().getVersion(); 162 if (version == null) 163 throw new HL7Exception("Need message version to add segment by name; message.getVersion() returns null"); 164 Class<? extends Structure> c = myFactory.getSegmentClass(theName, version); 165 if (c == null) 166 c = GenericSegment.class; 167 168 tryToInstantiateStructure(c, theName); //may throw exception 169 170 return insert(c, false, true, theIndex, theName); 171 } 172 173 /** 174 * Returns an ordered array of the names of the Structures in this 175 * Group. These names can be used to iterate through the group using 176 * repeated calls to <code>get(name)</code>. 177 */ 178 public String[] getNames() { 179 String[] retVal = new String[this.names.size()]; 180 for (int i = 0; i < this.names.size(); i++) { 181 retVal[i] = this.names.get(i); 182 } 183 return retVal; 184 } 185 186 /** 187 * Adds a new Structure (group or segment) to this Group. A place for the 188 * Structure is added to the group but there are initially zero repetitions. 189 * This method should be used by the constructors of implementing classes 190 * to specify which Structures the Group contains - Structures should be 191 * added in the order in which they appear. 192 * Note that the class is supplied instead of an instance because we want 193 * there initially to be zero instances of each structure but we want the 194 * AbstractGroup code to be able to create instances as necessary to support 195 * get(...) calls. 196 * @return the actual name used to store this structure (may be appended with 197 * an integer if there are duplicates in the same Group). 198 */ 199 protected String add(Class<? extends Structure> c, boolean required, boolean repeating) throws HL7Exception { 200 String name = getName(c); 201 202 return insert(c, required, repeating, this.names.size(), name); 203 } 204 205 206 /** 207 * Adds a new Structure (group or segment) to this Group. A place for the 208 * Structure is added to the group but there are initially zero repetitions. 209 * This method should be used by the constructors of implementing classes 210 * to specify which Structures the Group contains - Structures should be 211 * added in the order in which they appear. 212 * Note that the class is supplied instead of an instance because we want 213 * there initially to be zero instances of each structure but we want the 214 * AbstractGroup code to be able to create instances as necessary to support 215 * get(...) calls. 216 * @return the actual name used to store this structure (may be appended with 217 * an integer if there are duplicates in the same Group). 218 */ 219 protected String add(Class<? extends Structure> c, boolean required, boolean repeating, int index) throws HL7Exception { 220 String name = getName(c); 221 222 return insert(c, required, repeating, index, name); 223 } 224 225 226 /** 227 * Returns true if the class name is already being used. 228 */ 229 private boolean nameExists(String name) { 230 boolean exists = false; 231 Object o = this.classes.get(name); 232 if (o != null) exists = true; 233 return exists; 234 } 235 236 /** 237 * Attempts to create an instance of the given class and return 238 * it as a Structure. 239 * @param c the Structure implementing class 240 * @param name an optional name of the structure (used by Generic structures; may be null) 241 */ 242 private Structure tryToInstantiateStructure(Class<? extends Structure> c, String name) throws HL7Exception { 243 Structure s = null; 244 try { 245 Object o = null; 246 if (GenericSegment.class.isAssignableFrom(c)) { 247 s = new GenericSegment(this, name); 248 } else if (GenericGroup.class.isAssignableFrom(c)) { 249 s = new GenericGroup(this, name, myFactory); 250 } else { 251 //first try to instantiate using constructor w/ Message args ... 252 try { 253 Class<?>[] argClasses = {Group.class, ModelClassFactory.class}; 254 Object[] argObjects = {this, myFactory}; 255 Constructor<?> con = c.getConstructor(argClasses); 256 o = con.newInstance(argObjects); 257 } catch (NoSuchMethodException nme) { 258 o = c.newInstance(); 259 } 260 if (!(o instanceof Structure)) 261 throw new HL7Exception("Class " + c.getName() + " does not implement " + 262 "ca.on.uhn.hl7.message.Structure", HL7Exception.APPLICATION_INTERNAL_ERROR); 263 s = (Structure)o; 264 } 265 } catch (Exception e) { 266 if (e instanceof HL7Exception) { 267 throw (HL7Exception) e; 268 } else { 269 throw new HL7Exception( 270 "Can't instantiate class " + c.getName(), 271 HL7Exception.APPLICATION_INTERNAL_ERROR, 272 e); 273 } 274 } 275 return s; 276 } 277 278 /** 279 * Returns the Message to which this segment belongs. 280 */ 281 public Message getMessage() { 282 Structure s = this; 283 while (!Message.class.isAssignableFrom(s.getClass())) { 284 s = s.getParent(); 285 } 286 return (Message) s; 287 } 288 289 /** Returns the parent group within which this structure exists (may be root 290 * message group). 291 */ 292 public Group getParent() { 293 return this.parent; 294 } 295 296 /** 297 * Returns true if the named structure is required. 298 */ 299 public boolean isRequired(String name) throws HL7Exception { 300 Object o = required.get(name); 301 if (o == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 302 Boolean req = (Boolean)o; 303 return req.booleanValue(); 304 } 305 306 /** 307 * Returns true if the named structure is required. 308 */ 309 public boolean isRepeating(String name) throws HL7Exception { 310 Object o = repeating.get(name); 311 if (o == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 312 Boolean rep = (Boolean)o; 313 return rep.booleanValue(); 314 } 315 316 /** 317 * Returns the number of existing repetitions of the named structure. 318 */ 319 public int currentReps(String name) throws HL7Exception { 320 ArrayList<Structure> list = structures.get(name); 321 if (list == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 322 return list.size(); 323 } 324 325 /** 326 * Returns an array of Structure objects by name. For example, if the Group contains 327 * an MSH segment and "MSH" is supplied then this call would return a 1-element array 328 * containing the MSH segment. Multiple elements are returned when the segment or 329 * group repeats. The array may be empty if no repetitions have been accessed 330 * yet using the get(...) methods. 331 * @throws HL7Exception if the named Structure is not part of this Group. 332 */ 333 public Structure[] getAll(String name) throws HL7Exception { 334 ArrayList<Structure> list = structures.get(name); 335 if (list == null) throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 336 Structure[] all = new Structure[list.size()]; 337 for (int i = 0; i < list.size(); i++) { 338 all[i] = list.get(i); 339 } 340 return all; 341 } 342 343 344 /** 345 * Removes a repetition of a given Structure objects by name. For example, if 346 * the Group contains 10 repititions an OBX segment and "OBX" is supplied 347 * with an index of 2, then this call would remove the 3rd repetition. Note that 348 * in this case, the Set ID field in the OBX segments would also need to be 349 * renumbered manually. 350 * 351 * @return The removed structure 352 * @throws HL7Exception if the named Structure is not part of this Group. 353 */ 354 public Structure removeRepetition(String name, int index) throws HL7Exception { 355 ArrayList<Structure> list = structures.get(name); 356 if (list == null) { 357 throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 358 } 359 if (list.size() == 0) { 360 throw new HL7Exception("Invalid index: " + index + ", structure " + name + " has no repetitions", HL7Exception.APPLICATION_INTERNAL_ERROR); 361 } 362 if (list.size() <= index) { 363 throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and " + (list.size() - 1), HL7Exception.APPLICATION_INTERNAL_ERROR); 364 } 365 366 return list.remove(index); 367 } 368 369 370 /** 371 * Inserts a repetition of a given Structure into repetitions of that structure by name. 372 * For example, if the Group contains 10 repititions an OBX segment and an OBX is supplied 373 * with an index of 2, then this call would insert the new repetition at 374 * index 2. Note that 375 * in this case, the Set ID field in the OBX segments would also need to be 376 * renumbered manually. 377 * 378 * @return The removed structure 379 * @throws HL7Exception if the named Structure is not part of this Group. 380 */ 381 protected void insertRepetition(Structure structure, int index) throws HL7Exception { 382 if (structure == null) { 383 throw new NullPointerException("Structure may not be null"); 384 } 385 386 if (structure.getMessage() != this.getMessage()) { 387 throw new HL7Exception("Structure does not belong to this message", HL7Exception.APPLICATION_INTERNAL_ERROR); 388 } 389 390 String name = structure.getName(); 391 ArrayList<Structure> list = structures.get(name); 392 393 if (list == null) { 394 throw new HL7Exception("The structure " + name + " does not exist in the group " + this.getClass().getName(), HL7Exception.APPLICATION_INTERNAL_ERROR); 395 } 396 if (list.size() < index) { 397 throw new HL7Exception("Invalid index: " + index + ", structure " + name + " must be between 0 and " + (list.size()), HL7Exception.APPLICATION_INTERNAL_ERROR); 398 } 399 400 list.add(index, structure); 401 } 402 403 404 /** 405 * Inserts a repetition of a given Structure into repetitions of that structure by name. 406 * For example, if the Group contains 10 repititions an OBX segment and an OBX is supplied 407 * with an index of 2, then this call would insert the new repetition at 408 * index 2. Note that 409 * in this case, the Set ID field in the OBX segments would also need to be 410 * renumbered manually. 411 * 412 * @return The removed structure 413 * @throws HL7Exception if the named Structure is not part of this Group. 414 */ 415 public Structure insertRepetition(String name, int index) throws HL7Exception { 416 if (name == null || name.length() == 0) { 417 throw new NullPointerException("Name may not be null/empty"); 418 } 419 420 Class<? extends Structure> structureClass = this.classes.get(name); 421 if (structureClass == null) { 422 throw new HL7Exception("Group " + this.getClass().getName() + " has no structure named " + name + ": Valid names: " + this.classes.keySet(), HL7Exception.APPLICATION_INTERNAL_ERROR); 423 } 424 425 Structure rep = tryToInstantiateStructure(structureClass, name); 426 insertRepetition(rep, index); 427 428 return rep; 429 } 430 431 432 /** 433 * Returns the Class of the Structure at the given name index. 434 */ 435 public Class<? extends Structure> getClass(String name) { 436 return classes.get(name); 437 } 438 439 /** 440 * Returns the class name (excluding package). 441 * @see Structure#getName() 442 */ 443 public String getName() { 444 return getName(this.getClass()); 445 } 446 447 //returns a name for a class of a Structure in this Message 448 private String getName(Class<? extends Structure> c) { 449 String fullName = c.getName(); 450 int dotLoc = fullName.lastIndexOf('.'); 451 String name = fullName.substring(dotLoc + 1, fullName.length()); 452 453 //remove message name prefix from group names for compatibility with getters ... 454 if (Group.class.isAssignableFrom(c) && !Message.class.isAssignableFrom(c)) { 455 String messageName = getMessage().getName(); 456 if (name.startsWith(messageName) && name.length() > messageName.length()) { 457 name = name.substring(messageName.length() + 1); 458 } 459 } 460 461 return name; 462 } 463 464 /** 465 * Inserts the given structure into this group, at the 466 * indicated index number. This method is used to support handling 467 * of unexpected segments (e.g. Z-segments). In contrast, specification 468 * of the group's normal children should be done at construction time, using the 469 * add(...) method. 470 */ 471 private String insert(Class<? extends Structure> c, boolean required, boolean repeating, int index, String name) throws HL7Exception { 472 //tryToInstantiateStructure(c, name); //may throw exception 473 474 //see if there is already something by this name and make a new name if necessary ... 475 if (nameExists(name)) { 476 int version = 2; 477 String newName = name; 478 while (nameExists(newName)) { 479 newName = name + version++; 480 } 481 name = newName; 482 } 483 484 this.names.add(index, name); 485 this.required.put(name, new Boolean(required)); 486 this.repeating.put(name, new Boolean(repeating)); 487 this.classes.put(name, c); 488 this.structures.put(name, new ArrayList<Structure>()); 489 490 return name; 491 } 492 493 /** 494 * Clears all data from this structure. 495 */ 496 public void clear() { 497 for (ArrayList<Structure> next : structures.values()) { 498 if (next != null) { 499 next.clear(); 500 } 501 } 502 } 503 504 public final ModelClassFactory getModelClassFactory() { 505 return myFactory; 506 } 507 508 }