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    }