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 "Terser.java". Description: 010 * "Wraps a message to provide access to fields using a more terse syntax." 011 * 012 * The Initial Developer of the Original Code is University Health Network. Copyright (C) 013 * 2002. 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.util; 029 030 import ca.uhn.hl7v2.model.*; 031 import ca.uhn.hl7v2.HL7Exception; 032 import java.util.StringTokenizer; 033 import ca.uhn.log.*; 034 035 /** 036 * <p>Wraps a message to provide access to fields using a terse location 037 * specification syntax. For example: </p> 038 * <p><code>terser.set("MSH-9-3", "ADT_A01");</code> <br> 039 * can be used instead of <br> 040 * <code>message.getMSH().getMessageType().getMessageStructure().setValue("ADT_A01"); </code> </p> 041 * <p>The syntax of a location spec is as follows: </p> 042 * <p>location_spec: <code>segment_path_spec "-" field ["(" rep ")"] ["-" component ["-" subcomponent]] </code></p> 043 * <p>... where rep, field, component, and subcomponent are integers (representing, respectively, 044 * the field repetition (starting at 0), and the field number, component number, and subcomponent 045 * numbers (starting at 1). Omitting the rep is equivalent to specifying 0; omitting the 046 * component or subcomponent is equivalent to specifying 1.</p> 047 * <p>The syntax for the segment_path_spec is as follows: </p> 048 * <p>segment_path_spec: </code> ["/"] (group_spec ["(" rep ")"] "/")* segment_spec ["(" rep ")"]</code></p> 049 * <p> ... where rep has the same meaning as for fields. A leading "/" indicates that navigation to the 050 * location begins at the root of the message; ommitting this indicates that navigation begins at the 051 * current location of the underlying SegmentFinder (see getFinder() -- this allows manual navigation 052 * if desired). The syntax for group_spec is: </p> 053 * <p>group_spec: <code>["."] group_name_pattern</code></p> 054 * <p>Here, a . indicates that the group should be searched for (using a SegmentFinder) starting at the 055 * current location in the message. The wildcards "*" and "?" represent any number of arbitrary characters, 056 * and a single arbitrary character, respectively. For example, "M*" and "?S?" match MSH. The first 057 * group with a name that matches the given group_name_pattern will be matched. </p> 058 * <p>The segment_spec is analogous to the group_spec. </p> 059 * <p>As another example, the following subcomponent in an SIU_S12 message: <p> 060 * <p><code>msg.getSIU_S12_RGSAISNTEAIGNTEAILNTEAIPNTE(1).getSIU_S12_AIGNTE().getAIG().getResourceGroup(1).getIdentifier();</code></p> 061 * </p> ... is referenced by all of the following location_spec: </p> 062 * <p><code>/SIU_S12_RGSAISNTEAIGNTEAILNTEAIPNTE(1)/SIU_S12_AIGNTE/AIG-5(1)-1 <br> 063 * /*AIG*(1)/SIU_S12_AIGNTE/AIG-5(1)-1 <br> 064 * /*AIG*(1)/.AIG-5(1) <code></p> 065 * <p>The search function only iterates through rep 0 of each group. Thus if rep 0 of the first group 066 * in this example was desired instead of rep 1, the following syntax would also work (since there is 067 * only one AIG segment position in SUI_S12): </p> 068 * <p><code>/.AIG-5(1)</code></p> 069 * @author Bryan Tripp 070 */ 071 public class Terser { 072 073 private SegmentFinder finder; 074 private static HapiLog log = HapiLogFactory.getHapiLog(Terser.class); 075 076 /** Creates a new instance of Terser */ 077 public Terser(Message message) { 078 finder = new SegmentFinder(message); 079 } 080 081 /** 082 * Returns the string value of the Primitive at the given location. 083 * @param segment the segment from which to get the primitive 084 * @param field the field number 085 * @param rep the field repetition 086 * @param component the component number (use 1 for primitive field) 087 * @param subcomponent the subcomponent number (use 1 for primitive component) 088 */ 089 public static String get(Segment segment, int field, int rep, int component, int subcomponent) throws HL7Exception { 090 Primitive prim = getPrimitive(segment, field, rep, component, subcomponent); 091 return prim.getValue(); 092 } 093 094 /** 095 * Sets the string value of the Primitive at the given location. 096 */ 097 public static void set(Segment segment, int field, int rep, int component, int subcomponent, String value) throws HL7Exception { 098 Primitive prim = getPrimitive(segment, field, rep, component, subcomponent); 099 prim.setValue(value); 100 } 101 102 /** 103 * Returns the Primitive object at the given location. 104 */ 105 private static Primitive getPrimitive(Segment segment, int field, int rep, int component, int subcomponent) throws HL7Exception { 106 Type type = segment.getField(field, rep); 107 return getPrimitive(type, component, subcomponent); 108 } 109 110 /** 111 * Returns the Primitive object at the given location in the given field. 112 * It is intended that the given type be at the field level, although extra components 113 * will be added blindly if, for example, you provide a primitive subcomponent instead 114 * and specify component or subcomponent > 1 115 */ 116 public static Primitive getPrimitive(Type type, int component, int subcomponent) { 117 Type comp = getComponent(type, component); 118 Type sub = getComponent(comp, subcomponent); 119 return getPrimitive(sub); 120 } 121 122 /** 123 * Attempts to extract a Primitive from the given type. If it's a composite, 124 * drills down through first components until a primitive is reached. 125 */ 126 private static Primitive getPrimitive(Type type) { 127 Primitive p = null; 128 if (Varies.class.isAssignableFrom(type.getClass())) { 129 p = getPrimitive(((Varies) type).getData()); 130 } else if (Composite.class.isAssignableFrom(type.getClass())) { 131 try { 132 p = getPrimitive(((Composite) type).getComponent(0)); 133 } catch (HL7Exception e) { 134 throw new Error("Internal error: HL7Exception thrown on Composite.getComponent(0)."); 135 } 136 } else if (type instanceof Primitive) { 137 p = (Primitive) type; 138 } 139 return p; 140 } 141 142 /** 143 * Returns the component (or sub-component, as the case may be) at the given 144 * index. If it does not exist, it is added as an "extra component". 145 * If comp > 1 is requested from a Varies with GenericPrimitive data, the 146 * data is set to GenericComposite (this avoids the creation of a chain of 147 * ExtraComponents on GenericPrimitives). 148 * Components are numbered from 1. 149 */ 150 private static Type getComponent(Type type, int comp) { 151 Type ret = null; 152 if (Varies.class.isAssignableFrom(type.getClass())) { 153 Varies v = (Varies) type; 154 155 try { 156 if (comp > 1 && GenericPrimitive.class.isAssignableFrom(v.getData().getClass())) 157 v.setData(new GenericComposite(v.getMessage())); 158 } catch (DataTypeException de) { 159 String message = "Unexpected exception copying data to generic composite: " + de.getMessage(); 160 log.error(message, de); 161 throw new Error(message); 162 } 163 164 ret = getComponent(v.getData(), comp); 165 } else { 166 if (Primitive.class.isAssignableFrom(type.getClass()) && comp == 1) { 167 ret = type; 168 } else if (GenericComposite.class.isAssignableFrom(type.getClass()) 169 || (Composite.class.isAssignableFrom(type.getClass()) && comp <= numStandardComponents(type))) { 170 //note that GenericComposite can return components > number of standard components 171 172 try { 173 ret = ((Composite) type).getComponent(comp - 1); 174 } catch (Exception e) { 175 throw new Error("Internal error: HL7Exception thrown on getComponent(x) where x < # standard components.", e); 176 } 177 } else { 178 ret = type.getExtraComponents().getComponent(comp - numStandardComponents(type) - 1); 179 } 180 } 181 return ret; 182 } 183 184 /** 185 * <p>Gets the string value of the field specified. See the class docs for syntax 186 * of the location spec. </p> 187 * <p>If a repetition is omitted for a repeating segment or field, the first rep is used. 188 * If the component or subcomponent is not specified for a composite field, the first 189 * component is used (this allows one to write code that will work with later versions of 190 * the HL7 standard). 191 */ 192 public String get(String spec) throws HL7Exception { 193 StringTokenizer tok = new StringTokenizer(spec, "-", false); 194 Segment segment = getSegment(tok.nextToken()); 195 196 int[] ind = getIndices(spec); 197 return get(segment, ind[0], ind[1], ind[2], ind[3]); 198 } 199 200 /** 201 * Returns the segment specified in the given segment_path_spec. 202 */ 203 public Segment getSegment(String segSpec) throws HL7Exception { 204 Segment seg = null; 205 206 if (segSpec.substring(0, 1).equals("/")) { 207 getFinder().reset(); 208 } 209 210 StringTokenizer tok = new StringTokenizer(segSpec, "/", false); 211 SegmentFinder finder = getFinder(); 212 while(tok.hasMoreTokens()) { 213 String pathSpec = tok.nextToken(); 214 Terser.PathSpec ps = parsePathSpec(pathSpec); 215 if (tok.hasMoreTokens()) { 216 ps.isGroup = true; 217 } else { 218 ps.isGroup = false; 219 } 220 221 if (ps.isGroup) { 222 Group g = null; 223 if (ps.find) { 224 g = finder.findGroup(ps.pattern, ps.rep); 225 } else { 226 g = finder.getGroup(ps.pattern, ps.rep); 227 } 228 finder = new SegmentFinder(g); 229 } else { 230 if (ps.find) { 231 seg = finder.findSegment(ps.pattern, ps.rep); 232 } else { 233 seg = finder.getSegment(ps.pattern, ps.rep); 234 } 235 } 236 } 237 238 return seg; 239 } 240 241 /** Gets path information from a path spec. */ 242 private PathSpec parsePathSpec(String spec) throws HL7Exception { 243 PathSpec ps = new PathSpec(); 244 245 if (spec.startsWith(".")) { 246 ps.find = true; 247 spec = spec.substring(1); 248 } else { 249 ps.find = false; 250 } 251 252 if (spec.length() == 0) { 253 throw new HL7Exception("Invalid path (some path element is either empty or contains only a dot)"); 254 } 255 StringTokenizer tok = new StringTokenizer(spec, "()", false); 256 ps.pattern = tok.nextToken(); 257 if (tok.hasMoreTokens()) { 258 String repString = tok.nextToken(); 259 try { 260 ps.rep = Integer.parseInt(repString); 261 } catch (NumberFormatException e) { 262 throw new HL7Exception(repString + " is not a valid rep #", HL7Exception.APPLICATION_INTERNAL_ERROR); 263 } 264 } else { 265 ps.rep = 0; 266 } 267 return ps; 268 } 269 270 /** 271 * Given a Terser path, returns an array containing field num, field rep, 272 * component, and subcomponent. 273 */ 274 public static int[] getIndices(String spec) throws HL7Exception { 275 StringTokenizer tok = new StringTokenizer(spec, "-", false); 276 tok.nextToken(); //skip over segment 277 if (!tok.hasMoreTokens()) 278 throw new HL7Exception("Must specify field in spec " + spec, HL7Exception.APPLICATION_INTERNAL_ERROR); 279 280 int[] ret = null; 281 try { 282 StringTokenizer fieldSpec = new StringTokenizer(tok.nextToken(), "()", false); 283 int fieldNum = Integer.parseInt(fieldSpec.nextToken()); 284 int fieldRep = 0; 285 if (fieldSpec.hasMoreTokens()) { 286 fieldRep = Integer.parseInt(fieldSpec.nextToken()); 287 } 288 289 int component = 1; 290 if (tok.hasMoreTokens()) { 291 component = Integer.parseInt(tok.nextToken()); 292 } 293 294 int subcomponent = 1; 295 if (tok.hasMoreTokens()) { 296 subcomponent = Integer.parseInt(tok.nextToken()); 297 } 298 int[] result = {fieldNum, fieldRep, component, subcomponent}; 299 ret = result; 300 } catch (NumberFormatException e) { 301 throw new HL7Exception("Invalid integer in spec " + spec, HL7Exception.APPLICATION_INTERNAL_ERROR); 302 } 303 304 return ret; 305 } 306 307 /** 308 * Sets the string value of the field specified. See class docs for location spec syntax. 309 */ 310 public void set(String spec, String value) throws HL7Exception { 311 StringTokenizer tok = new StringTokenizer(spec, "-", false); 312 Segment segment = getSegment(tok.nextToken()); 313 314 int[] ind = getIndices(spec); 315 if (log.isDebugEnabled()) { 316 log.debug("Setting " + spec + " seg: " + segment.getName() + " ind: " + ind[0] + " " + ind[1] + " " + ind[2] + " " + ind[3]); 317 } 318 set(segment, ind[0], ind[1], ind[2], ind[3], value); 319 } 320 321 /** 322 * Returns the number of components in the given field, i.e. the 323 * number of standard components (e.g. 6 for CE) plus any extra components that 324 * have been added at runtime. This may vary by repetition, as different reps 325 * may have different extra components. 326 */ 327 /*public static int numComponents(Type field) throws HL7Exception { 328 return numComponents(seg.getField(field, rep)); 329 }*/ 330 331 /** 332 * Returns the number of sub-components in the specified component, i.e. 333 * the number of standard sub-components (e.g. 6 for CE) plus any extra components that 334 * that have been added at runtime. 335 * @param component numbered from 1 336 */ 337 public static int numSubComponents(Type type, int component) { 338 int n = -1; 339 if (component == 1 && Primitive.class.isAssignableFrom(type.getClass())) { 340 //note that getComponent(primitive, 1) below returns the primitive 341 //itself -- if we do numComponents on it, we'll end up with the 342 //number of components in the field, not the number of subcomponents 343 n = 1; 344 } else { 345 Type comp = getComponent(type, component); 346 n = numComponents(comp); 347 } 348 return n; 349 /* 350 //Type t = seg.getField(field, rep); 351 if (Varies.class.isAssignableFrom(type.getClass())) { 352 return numSubComponents(((Varies) type).getData(), component); 353 } else if (Primitive.class.isAssignableFrom(type.getClass()) && component == 1) { 354 n = 1; 355 } else if (Composite.class.isAssignableFrom(type.getClass()) && component <= numStandardComponents(t)) { 356 n = numComponents(((Composite) type).getComponent(component - 1)); 357 } else { //we're being asked about subcomponents of an extra component 358 n = numComponents(t.getExtraComponents().getComponent(component - numStandardComponents(t) - 1)); 359 } 360 return n; 361 */ 362 } 363 364 /** 365 * Returns the number of components in the given type, i.e. the 366 * number of standard components (e.g. 6 for CE) plus any extra components that 367 * have been added at runtime. 368 */ 369 public static int numComponents(Type type) { 370 if (Varies.class.isAssignableFrom(type.getClass())) { 371 return numComponents(((Varies) type).getData()); 372 } else { 373 return numStandardComponents(type) + type.getExtraComponents().numComponents(); 374 } 375 } 376 377 private static int numStandardComponents(Type t) { 378 int n = 0; 379 if (Varies.class.isAssignableFrom(t.getClass())) { 380 n = numStandardComponents(((Varies) t).getData()); 381 } else if (Composite.class.isAssignableFrom(t.getClass())) { 382 n = ((Composite) t).getComponents().length; 383 } else { 384 n = 1; 385 } 386 return n; 387 } 388 389 /** 390 * Returns the segment finder used by this Terser. Navigating the 391 * finder will influence the behaviour of the Terser accordingly. Ie 392 * when the full path of the segment is not specified the segment will 393 * be sought beginning at the current location of the finder. 394 */ 395 public SegmentFinder getFinder() { 396 return finder; 397 } 398 399 /** Struct for information about a step in a segment path. */ 400 private class PathSpec { 401 public String pattern; 402 public boolean isGroup; 403 public boolean find; 404 public int rep; 405 } 406 }