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